Alejandro Guerra-Hernández Metodologı́as de Programación I Programación Lógica 5 de noviembre de 2009 Departamento de Inteligencia Artificial Sebastián Camacho No. 5, Xalapa, Ver., México 91000 Índice general 1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1. Breve historia de la programación lógica. . . . . . . . . . . . . . . . . . . . . . . . 1 1.2. Una breve introducción a Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2.1. Hechos y relaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2.2. Reglas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2.3. Definición de reglas recursivas . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.3. ¿Cómo computa Prolog una solución? . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.4. Organización del curso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Parte I Formalismos 2. Lógica de Primer Orden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2. Sistemas formales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3. El lenguaje de la lógica de primer orden . . . . . . . . . . . . . . . . . . . . . . . . 2.3.1. Sintaxis de la lógica de primer orden . . . . . . . . . . . . . . . . . . . . 2.4. La semántica de la lógica de primer orden . . . . . . . . . . . . . . . . . . . . . . 2.4.1. Teorı́a de modelo de la lógica de primer orden . . . . . . . . . . . . 2.5. Inferencia en la lógica de primer orden . . . . . . . . . . . . . . . . . . . . . . . . . 2.6. Substituciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 23 23 25 26 27 29 31 3. Cláusulas y Programas Definitivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1. Cláusulas definitivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Programas definitivos y Metas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3. El modelo mı́nimo de Herbrand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1. Resultados concernientes a los modelos de Herbrand . . . . . . 3.3.2. Construcción del modelo mı́nimo de Herbrand . . . . . . . . . . . . 33 33 35 37 39 41 4. Principio de Resolución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.2. ¿Qué es un procedimiento de prueba? . . . . . . . . . . . . . . . . . . . . . . . . . . 44 V Índice general VI 4.3. 4.4. 4.5. 4.6. Pruebas y programas lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Substitución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Resolución-SLD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1. Propiedades de la resolución-SLD . . . . . . . . . . . . . . . . . . . . . . 45 48 50 52 55 5. Negación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2. La compleción de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3. Resolución SLDNF para programas definitivos . . . . . . . . . . . . . . . . . . 5.4. Programas Lógicos Generales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5. Resolución SLDNF para programas generales . . . . . . . . . . . . . . . . . . . 57 57 59 62 65 67 6. Corte y Aritmética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 6.1. Corte: podando el árbol-SLD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 6.2. Aritmética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Parte II Prolog 7. Introducción a Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 8. Estrategias básicas de resolución de problemas . . . . . . . . . . . . . . . . . . . . 8.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2. Búsqueda primero en profundidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3. Búsqueda primero en amplitud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.4. Búsqueda primero el mejor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9. Sistemas Expertos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 9.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 9.2. Caracterı́sticas de los SE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 9.2.1. Razonamiento basado en metas . . . . . . . . . . . . . . . . . . . . . . . . 101 9.2.2. Incertidumbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 9.2.3. Razonamiento guiado por los datos . . . . . . . . . . . . . . . . . . . . . 102 9.3. Usando la máquina de inferencia de Prolog . . . . . . . . . . . . . . . . . . . . . 103 9.3.1. Reglas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 9.3.2. Reglas para relaciones jerárquicas . . . . . . . . . . . . . . . . . . . . . . 104 9.3.3. Reglas para otras relaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 9.4. Interfaz del usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 9.5. Un Shell simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 9.5.1. REPL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 9.6. Encadenamiento hacı́a atrás con incertidumbre . . . . . . . . . . . . . . . . . . 111 9.6.1. Factores de certidumbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 9.6.2. Factores de certidumbre à la MYCIN . . . . . . . . . . . . . . . . . . . 114 9.6.3. Formato de las reglas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 9.6.4. La máquina de inferencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 9.6.5. Interfaz con el usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 85 85 87 89 92 Índice general VII 10. Arboles de Decisión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 10.1. Representación de los árboles de decisión . . . . . . . . . . . . . . . . . . . . . . 121 10.2. Problemas apropiados para la aplicación de árboles de decisión . . . . 123 10.3. El algoritmo básico de aprendizaje de árboles de decisión . . . . . . . . . 124 10.3.1. ¿Qué atributo es el mejor clasificador? . . . . . . . . . . . . . . . . . . 124 10.3.2. Entropı́a y ganancia de información . . . . . . . . . . . . . . . . . . . . . 126 10.4. Espacio de hipótesis en el aprendizaje inductivo de árboles de decisión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 10.5. Sesgo inductivo en el aprendizaje de árboles de decisión . . . . . . . . . . 128 10.5.1. Sesgo por restricción y sesgo por preferencia . . . . . . . . . . . . . 129 10.5.2. ¿Porqué preferir hipótesis más compactas? . . . . . . . . . . . . . . . 129 10.6. Consideraciones sobre el aprendizaje inductivo de árboles de decisión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 10.6.1. Evitando un sobreajuste con los datos de entrenamiento . . . . 130 10.6.2. Incorporando valores contı́nuos . . . . . . . . . . . . . . . . . . . . . . . . 132 10.6.3. Medidas alternativas para la selección de atributos . . . . . . . . 133 10.7. Implementación el Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 10.7.1. Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 10.7.2. Distribución de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 10.7.3. El mejor atributo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 10.7.4. El árbol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 10.7.5. Imprimiendo el árbol construido. . . . . . . . . . . . . . . . . . . . . . . . 139 10.7.6. Ejecutando todo el experimento . . . . . . . . . . . . . . . . . . . . . . . . 140 10.7.7. Predicados auxiliares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 11. Planeación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 11.1. Acciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 11.2. Análisis medios-fines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 11.3. Metas protegidas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 11.4. Aspectos procedimentales de la búsqueda en amplitud . . . . . . . . . . . . 149 11.5. Regresión de metas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 11.6. Combinando planeación medios fines con primero el mejor . . . . . . . 154 11.7. Variables y planes no lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 11.7.1. Acciones y metas no instanciadas . . . . . . . . . . . . . . . . . . . . . . . 159 11.7.2. Planes no lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Acrónimos ⇒ CWA fbf FOL FOPC IA LFOL MGU NAF R ssi WAM U Implicación material Suposición del mundo cerrado (Closed World Assumption). Fórmula bien formada (wff - well formed formula). Lógica de primer orden (First-Order Logic). Cálculo de predicados en primer orden (First-Order Predicate Calculus). Inteligencia Artificial. El lenguaje de la lógica de primer orden. Unificador más general (Most General Unifier). Negación por fallo finito (Negation as Finite Failure). Función de selección en la resolución-SLD. Si y sólo si. Máquina abstracta de Warren (Warren Abstract Machine). Universo de discurso. En ciertas ocasiones se presentará como D (dominio). IX Capı́tulo 1 Introducción Resumen El tema de este curso de metodologı́as de programación es la programación lógica. En este capı́tulo se presenta un panorama general de este paradigma de programación, con el objetivo de que ustedes puedan responder a ¿Porqué y para qué es necesario estudiar a la programación lógica en un curso de su maestrı́a en Inteligencia Artificial (IA)? Para ello, se revisarán algunas notas históricas sobre los origenes de la programación lógica y Prolog, su lenguaje de programación más conocido; se experimentará con el uso de Prolog; y se revisará brevemente cuales son los fundamentos teóricos del paradigma. Finalmente, el capı́tulo ofrece la organización del material que revisaremos en el resto del curso. 1.1. Breve historia de la programación lógica. La historia reciente, y à la française, de la programación lógica comienza en julio de 1970 en Montreal, Canadá, donde Alain Colmerauer, Philippe Roussel y Robert Pasero [4] trabajaban en un proyecto sobre traducción automática y procesamiento del lenguaje natural. El sistema en cuestión incluı́a analizadores sintácticos y generadores de frases para el francés. Un estudiante de Colmerauer, decidió trabajar sobre la demostración automática de teoremas, con base en el trabajo sobre el principio de resolución de Alan Robinson [15]. La conjunción de estos trabajos dio como resultado una interfase entre el francés y las fórmulas lógicas del demostrador de teoremas que permitı́a interacciones como que se muestra en el cuadro 1.1. Este sistema hacı́a uso de constantes para designar elementos (Tom, Jerry, Max, Queso); para designar conjuntos (Gatos, Ratones, Ratones que comen queso, etc.); y relaciones binarias (Matar, Comer, Gustar, No gustar). Las constantes, junto con los sı́mbolos funcionales T he, Subset, y True, especificaban un lenguaje de fórmulas lógicas. Mientras se seguı́a trabajando en la demostración de teoremas en este lenguaje, apareció la referencia obligada al trabajo de Robert Kowalski [7] sobre el método conocido como resolución-SL, que como veremos, es fundamental en el lenguaje Prolog. 1 2 1 Introducción Usuario > Los gatos matan ratones. Usuario > Tom es un gato al que no le gustan los ratones que comen queso. Usuario > Jerry es un ratón que come queso. Usuario > Max no es un gato. Usuario > ¿Qué hace Tom? Computadora > A Tom no le gustan los ratones que comen queso. Computadora > Tom mata ratones. Usuario > ¿Quién es un gato? Computadora > Tom. Usuario > ¿Qué come Jerry? Computadora > Queso. Usuario > ¿Qué come Tom? Computadora > Lo que comen los gatos a los que no les gustan los ratones que comen queso. Cuadro 1.1 Sistema de lenguaje natural de Alain Colmerauer et al. [4]. En realidad, la creación de este paradigma de programación tiene una larga historia más universal, cuya mayor parte transcurre en los dominios de la lógica matemática y recientemente en el de las ciencias de la computación. La programación lógica se basa en la sintaxis de la lógica de primer orden, originalmente propuesta por Gottlob Frege en la segunda mitad del siglo XIX y modificada a su forma actual por Giuseppe Peano y Bertrand Russell. En la década de los años treinta, Kurt Göedel y Jacques Herbrand estudiaron la noción de computabilidad basada en derivaciones. Su trabajo puede verse como el origen de la “computación como deducción”. Además, Herbrand discutió en su tesis doctoral un conjunto de reglas para manipular ecuaciones algebraicas en términos que pueden verse ahora como un bosquejo de la unificación. Treinta años más tarde, Alan Robinson [15] publicó su artı́culo fundacional sobre la demostración automática. En este trabajo se introduce el principio de resolución, la noción de unificación y un algoritmo de unificación. Y es que, si bien el paradigma de la programación lógica tiene sus raı́ces en la demostración automática de teoremas, de donde tomó la noción de deducción, presenta una novedad importante: en el proceso de demostración algunos valores serán computados. Pero otro paso era necesario para vislumbrar como es posible computar en este marco. En 1974, Robert Kowalski [6] introduce la noción de programas lógicos con una forma restringida de resolución. La sintaxis propuesta por Kowalski era más restringida que la de Robinson, pero tenı́a un efecto colateral sobre la forma de una substitución satisfactoria. Esta substitución puede verse como el resultado de una computación, y en consecuencia, ciertas fórmulas lógicas (cláusulas de Horn) pueden interpretarse como programas. El trabajo de Kowalski termino un debate del todo relevante para nosotros: dadas las metas de la inteligencia artificial ¿El conocimiento debe representarse de forma declarativa o procedimental? Si la forma declarativa era la adecuada, tal como defendı́a John McCarthy [8], la realización de la inteligencia artificial pasaba por representar el conocimiento en cálculo de predicados e implementar procedimientos de prueba eficientes sobre este lenguaje; 1.2 Una breve introducción a Prolog 3 Si la forma procedimental era la adecuada, entonces tal realización pasaba por la implementación de procedimientos organizados como una sociedad de agentes que compiten y cooperan, tal como lo resume Marvin Minsky [9]. Los programas lógicos de Kowalski tienen evidentemente una interpretación declarativa, pero también procedimental. Entre 1971 y 1973 Kowalski y Colmeraruer colaboraron intensamente, concluyendo con la creación de Prolog en 1973. Prolog puede verse como la realización práctica del concepto de programa lógico. Aunque sus inicios estuvieron enfocados al procesamiento del lenguaje natural, pronto se encontró que Prolog podı́a ser usado como un lenguaje de programación de propósito general. Originalmente, Prolog fue implementado por Philippe Roussel como un intérprete escrito en Algol-W. Un paso adelante fue logrado por David H. Warren [20] quién propuso en 1983 una máquina abstracta, ahora conocida como WAM (Warren Abstract Machine). La WAM cuenta con un conjunto de instrucciones para compiladores de Prolog independientes de la máquina y se convirtió en el estándar para la implementación de Prolog y otros lenguajes lógicos de programación. De esta breve historia (para una versión más detallada ver J.A. Robinson [16]) podemos extraer algunas consideraciones sobre este curso: La programación lógica es una herramienta y un sujeto de estudio de la inteligencia artificial. La lógica de primer orden es fundamental para entender este paradigma de programación. La programación lógica es un paradigma de programación, que difiere de otros paradigmas, como la programación imperativa (Algol, C, Pascal, etc.), la orientada a objetos (Simula, Smalltalk, Eiffel, C++, Java, etc.), o la funcional (ML, Haskell, Lisp, etc.). Prolog 6= programación lógica, pero es su realización práctica más usada en la actualidad. 1.2. Una breve introducción a Prolog Prolog es la realización más utilizada del paradigma de programación lógica. Escribir un programa en Prolog tiene menos que ver con la tarea de especificar un algoritmo, como es el caso de la programación imperativa; y más con la especificación de los objetos y las relaciones entre ellos, que ocurren en el contexto de un problema. En particular, tiene que ver con la especificación de las relaciones que conforman la solución deseada del problema. Veamos un ejemplo basado en la genealogı́a de una familia [1]. 4 1.2.1. 1 Introducción Hechos y relaciones La figura 1.1 muestra una relación familiar, donde las flechas X → Y indican que X es progenitor Y . El hecho de que Tom sea progenitor de Bob 1 se escribe en Prolog: progenitor(tom,bob). ann pam tom bob liz pat jim Figura 1.1 Una relación familiar. Hemos escogido progenitor como el nombre de una relación que tiene a tom y bob como argumentos. Por razones que explicaremos más adelante, escribimos los nombres como tom con minúscula inicial. Para indicar que esta relación tiene dos argumentos escribimos progenitor/2 y decimos que progenitor tiene aridad 2. El árbol familiar completo puede definirse como un programa en Prolog: 1 2 3 4 5 6 progenitor(pam,bob). progenitor(tom,bob). progenitor(tom,liz). progenitor(bob,ann). progenitor(bob,pat). progenitor(pat,jim). Este programa consta de seis cláusulas. Cada una de estas cláusulas declara un hecho sobre la relación progenitor. Por ejemplo, progenitor(tom,bob) es un caso particular de la relación progenitor. Una relación está definida por el conjunto de todos sus casos. Podemos editar un archivo con este programa Prolog y llamarlo clase01.pl. Para utilizar este programa es necesario invocar a Prolog, por ejemplo, si usamos 1 Decidı́ usar una familia gringa, porque nuestros bellos nombres como Marı́a del Pilar, no caben en un grafo fácil de leer. Si usted quiere llamar a Tom, Pancho; eso, como veremos, no cambia en nada la historia que voy a contar (a condición de que Pancho sea siempre Pancho). 1.2 Una breve introducción a Prolog 5 SWI Prolog, en una terminal invocarı́amos swipl (ó pl en algunos sistemas operativos): > swipl Welcome to SWI-Prolog (Multi-threaded, 32 bits, Version 5.6.64) Copyright (c) 1990-2008 University of Amsterdam. SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. Please visit http://www.swi-prolog.org for details. For help, use ?- help(Topic). or ?- apropos(Word). ?- El sı́mbolo ?- es el indicador de que Prolog espera una instrucción. Si tenemos un archivo llamado clase01.pl con el conjunto de casos que define la relación progenitor, podemos consultarla desde SWI Prolog: ?- [clase01]. % clase01 compiled 0.00 sec, 168 bytes Yes ?- Prolog responde que el programa clase01 ha sido compilado (¿Sabı́an ustedes que el código de Prolog es compilado?) y espera una nueva instrucción. La instrucción puede ser la pregunta ¿Es progenitor Bob de Pat? ?- progenitor(bob,pat). Yes a lo que Prolog responderá Yes, al encontrar que ese hecho se encuentra en nuestro programa. Si preguntamos ¿Es Liz progenitora de Pat? obtendremos como respuesta No, porque nuestro programa no menciona nada (¿Habı́an escuchado el termino “supuesto del mundo cerrado”?) acerca de que Liz sea progenitora de Pat: ?- progenitor(liz,pat). No Lo mismo sucede con la siguiente consulta, pues Ben no es siquiera un objeto conocido por nuestro programa, esto es, Ben no aparece en ninguna parte de nuestro código: ?- progenitor(tom,ben). No Una pregunta más interesante sobre la relación progenitor es ¿Quién es el progenitor de Liz? Lo cual puede preguntarse como: ?- progenitor(X,liz). X = tom Yes 6 1 Introducción Prolog computa un valor para X tal que la relación progenitor se cumple. Si preguntamos por los hijos de Bob, tendremos varı́as respuestas posibles. Para obtenerlas todas, es necesario teclear ; y ←- o Enter después de cada respuesta de Prolog: ?- progenitor(bob,X). X = ann ; X = pat ; No Prolog nos da las respuestas ann, pat y al no haber más respuestas posibles, responde No. Es posible plantear preguntas más complicadas a nuestro programa, por ejemplo ¿Quién es abuelo/a de Jim? Como nuestro programa no conoce directamente la relación abuelo/2, esta pregunta debe descomponerse en dos preguntas como lo muestra la figura 1.2: 1. ¿Quién es el progenitor de Jim? Asumamos que es alguién Y . 2. ¿Quién es el progenitor de Y? Asumamos que es alguién X. X progenitor Y abuelo progenitor jim Figura 1.2 La relación abuelo expresada como una composición de dos relaciones progenitor. La secuencia de preguntas en Prolog es como sigue: ?- progenitor(Y,jim), progenitor(X,Y). Y = pat X = bob Yes Si invertimos el orden de las dos preguntas, el resultado sigue siendo el mismo: ?- progenitor(X,Y), progenitor(Y,jim). X = bob Y = pat Yes 1.2 Una breve introducción a Prolog 7 Podemos preguntar también ¿Quien es nieto de Tom?: ?- progenitor(tom,X), progenitor(X,Y). X = bob Y = ann ; X = bob Y = pat ; No Otra pregunta interesante serı́a ¿Tienen Ann y Pat progenitores en común? Esto puede descomponerse nuevamente en dos preguntas: 1. ¿Quién es el progenitor X de Ann? 2. ¿Es X (el mismo) progenitor de Pat? ?- progenitor(X,ann), progenitor(X,pat). X = bob ; No ?- Resumiendo: Es sencillo definir en Prolog una relación, como progenitor/2, especificando las n-tuplas de objetos que satisfacen la relación (n, conocido como aridad, es el número de argumentos de la relación, para progenitor n = 2). El usuario puede plantear fácilmente preguntas a Prolog sobre las relaciones definidas en un programa. Un programa Prolog consiste de cláusulas. Cada cláusula termina con un punto. Los argumentos de una relación pueden ser: objetos concretos o constantes como tom y ann; objetos generales o variables como X e Y. Las preguntas planteadas a Prolog consisten en una o más metas. Una secuencia de metas como progenitor(X,ann), progenitor(X,pat) significa la conjunción de las metas: X es progenitor de ann y X es progenitor de pat. La respuesta a una pregunta puede ser positiva o negativa, dependiendo de si la meta se puede satisfacer o no. En el caso de una respuesta positiva, se dice que la meta fue satisfecha y tuvo éxito. En cualquier otro caso se dice que la meta no fue satisfecha y falló. Si varias respuestas satisfacen una pregunta, Prolog encontrará tantas como el usuario quiera. 1.2.2. Reglas Nuestro ejemplo puede extenderse en muchas formas interesantes. Definamos las relaciones mu jer/1 y hombre/1, para poder expresarnos sobre el genero de los miembros de nuestra familia ejemplar: 8 1 2 3 4 5 6 7 1 Introducción mujer(pam). mujer(liz). mujer(pat). mujer(ann). hombre(tom). hombre(bob). hombre(jim). Las relaciones unarias (n = 1) se usan normalmente para expresar propiedades de los objetos. Las relaciones binarias (n = 2) definen relaciones entre pares de objetos. La cláusula mujer(pam) establece que Pam es una mujer. La misma información podrı́a definirse como una relación genero/2 como genero(pam,mujer). Nuestra siguiente extensión al programa será definir la relación vastago/2 como la inversa de la relación progenitor/2. Para ello podemos definir explı́citamente las tuplas que satisfacen esta relación, por ejemplo: vastago(liz,tom, etc. Sin embargo, se puede obtener una definición más elegante si tomamos en cuenta que la relación vastago/2 es la inversa de progenitor/2 y que progenitor/2 ya fue definida. La alternativa se basa en el siguiente enunciado lógico: Para todo X y para todo Y , Y es un vástago de X si existe un X que es progenitor de un Y . Esta formulación es muy parecida al formalismo usado en Prolog. La cláusula correspondiente es la siguiente: 1 vastago(Y,X) :- progenitor(X,Y). La cláusula puede leerse también como: Si X es un progenitor de Y entonces Y es un vástago de X. A este tipo de cláusulas se les conoce como reglas. Existe una diferencia fundamental entre los hechos y las reglas. Un hecho como progenitor(tom,liz) es algo que es siempre, incondicionalmente, verdadero. Las reglas especifican cosas que son ciertas si alguna condición se satisface. Por ello decimos que las reglas tienen: Una parte condicional (el lado derecho de la regla o cuerpo de la regla). Una conclusión (el lado izquierdo de la regla o cabeza de la regla). ¿Qué hace Prolog cuando se le plantea una meta como la siguiente? ?- vastago(liz,tom). No existe ningún hecho sobre vástagos en nuestro programa, por lo tanto, la única alternativa es considerar la aplicación de la regla sobre los vástagos. La regla es general, en el sentido que es aplicable a cualquier objeto X e Y, por lo que puede ser aplicada a constantes como liz y tom. Para aplicar la regla a liz y a tom es necesario substituir Y por liz y X por tom. Con tal substitución, obtenemos un caso especial de nuestra regla: vastago(liz,tom) :- progenitor(tom,liz). 1.2 Una breve introducción a Prolog 9 La parte condicional de la regla es ahora: progenitor(tom,liz). Ahora Prolog tratará de encontrar si esta condición es verdadera, de forma que la meta inicial: vastago(liz,tom). ha sido substituida por una sub-meta progenitor(tom,liz). Esta nueva sub-meta puede satisfacerse fácilmente a partir de los hechos conocidos por el programa, lo cual significa que la conclusión de la regla también es verdadera, y Prolog responde con éxito: ?- vastago(liz,tom). Yes Especifiquemos ahora la relación madre/2 a partir del siguiente enunciado lógico: Para toda X e Y , X es madre de Y si X es progenitor de Y y X es mujer. Esto se traduce a Prolog como: 1 madre(X,Y) :- progenitor(X,Y), mujer(X). La coma en el cuerpo de la regla, indica una conjunción: ambas condiciones deben ser verdaderas para que la conclusión lo sea. Las relaciones abuela/2 y hermana/2 pueden definirse como: 1 2 abuela(X,Y) :- progenitor(X,Z), progenitor(Z,Y), mujer(X). hermana(X,Y) :- progenitor(Z,X), progenitor(Z,Y), mujer(X). Observen, en el caso de hermana/2, la manera de especificar que X e Y tienen un mismo progenitor. La condición de esta regla se lee: existe un Z que es progenitor de X y el mismo Z es progenitor de Y y X es mujer. Gráficamente la relación hermana/2 se muestra en la figura 1.3. Ahora podemos preguntar: ?- hermana(ann,pat). Yes Tras nuestra primer pregunta sobre esta relación, podemos concluir que su definición es correcta, pero tiene un sutil error que se revela al preguntar: ?- hermana(X,pat). X = ann ; X = pat ; No 10 1 Introducción Z progenitor mujer X progenitor hermana Y Figura 1.3 La relación hermana. ¿Es correcto que Pat sea su propia hermana? Ese es el comportamiento que esperábamos de la definición de hermana/2, y se debe a que no hay nada que diga que X e Y deben ser ¡diferentes! Esto se puede corregir definiendo hermana como: 1 2 3 4 5 hermana(X,Y) :progenitor(Z,X), progenitor(Z,Y), mujer(X), dif(X,Y). De forma que: ?- hermana(X,pat). X = ann ; No Resumiendo: Los programas Prolog pueden extenderse fácilmente agregando nuevas cláusulas. Las cláusulas en Prolog son de tres tipos: hechos, reglas y metas. Los hechos declaran cosas que son verdaderas siempre, incondicionalmente. Las reglas declaran cosas que son verdaderas dependiendo de ciertas condiciones. Por medio de las preguntas el usuario puede computar qué cosas son verdaderas. Las cláusulas de Prolog tienen cabeza y cuerpo. El cuerpo es una lista de metas separadas por comas. Las comas implican conjunción. Los hechos son cláusulas con el cuerpo vacı́o; las preguntas tienen la cabeza vacı́a; y las reglas tienen cabeza y cuerpo. En el curso de una computación, las variables pueden ser substituidas por otros objetos. Las variables se asumen cuantificadas universalmente. La cuantificación existencial sólo es posible en las variables que aparecen en el cuerpo de una cláusula. Por 1.2 Una breve introducción a Prolog 11 ejemplo la cláusula tiene hijo(X) :- progenitor(X,Y) puede leerse como: Para todo X, X tiene un hijo si existe un Y y X es progenitor de Y . 1.2.3. Definición de reglas recursivas Agreguemos una relación nueva a nuestro programa: la relación ancestro/2. Esta relación será definida en términos de la relación progenitor/2. La definición completa puede expresarse por medio de dos reglas. La primera definiendo al ancestro inmediato (progenitor) y la segunda a los ancestros no inmediatos. Decimos que alguien X es ancestro indirecto de alguien Z, si hay una cadena de progenitores desde X hasta Z, como lo ilustra la figura 1.4. En nuestro ejemplo de la figura 1.1, Tom es ancestro directo de Liz e indirecto de Pat. X progenitor progenitor ancestro X ancestro progenitor Y progenitor Y Figura 1.4 La relación ancestro en términos de progenitor directo e indirecto. La primera regla es muy sencilla y se expresa en Prolog como: 1 ancestro(X,Z) :- progenitor(X,Z). La segunda regla es más complicada porque las cadenas de progenitores presentan un problema: ¡no sabemos cuantas veces hay que aplicar la relación progenitor! Un primer intento podrı́a ser algo como: 1 2 3 4 ancestro(X,Z) :progenitor(X,Z). ancestro(X,Z) :progenitor(X,Y), 12 5 6 7 8 9 10 1 Introducción progenitor(Y,Z). ancestro(X,Z) :progenitor(X,Y0), progenitor(Y0,Y1), progenitor(Y1,Z). ... Lo cual resulta en un programa largo y, peor aún, que sólo funciona para un número limitado de ancestros, en el ejemplo: padres, abuelos y bisabuelos. Es decir, esta definición de ancestro/2 es correcta pero incompleta. Existe una formulación elegante y completa de la relación ancestro/2, completa en el sentido que puede computar cualquier ancestro, independientemente de la longitud de la cadena de progenitores que deba aplicarse. La idea central es definir ancestro en términos de si misma: 1 2 ancestro(X,Z) :progenitor(X,Z). 3 4 5 6 ancestro(X,Z) :progenitor(X,Y), ancestro(Y,Z). Ahora podemos preguntar ¿De quien es ancestro Pam? ?- ancestro(pam,X). X = bob ; X = ann ; X = pat ; X = jim ; No O ¿Quienes son los ancestros de Jim? ?- ancestro(X,jim). X = pat ; X = pam ; X = tom ; X = bob ; No Resumiendo: Las reglas recursivas definen conceptos en términos de ellos mismos. Están definidas por al menos dos casos: uno terminal (no recursivo) y la llamada recursiva. Una relación recursiva define intenSionalmente un concepto. intenSional 6= intenCional. 1.3 ¿Cómo computa Prolog una solución? 1.3. 13 ¿Cómo computa Prolog una solución? Una pregunta a Prolog es siempre una secuencia de una o más metas. Para responder, Prolog trata de satisfacer estas metas. ¿Qué significa satisfacer una meta? Satisfacer una meta implica demostrar que la meta es verdadera, asumiendo que las relaciones en el programa lógico son verdaderas. Satisfacer una meta significa entonces demostrar que la meta es una consecuencia lógica de los hechos y reglas definidas en un programa. Si la pregunta contiene variables, Prolog necesita también encontrar cuales son los objetos particulares (que remplazaran a las variables) para los cuales la meta se satisface. La asignación de valores a variables es mostrada al usuario. Si Prolog no puede demostrar para alguna asignación de valores a variables, que las metas siguen lógicamente del programa, la respuesta a la pregunta será No. En términos matemáticos, la interpretación de un programa en Prolog es como sigue: Prolog acepta hechos y reglas como un conjunto de axiomas, y el usuario plantea preguntas como un teorema; entonces Prolog trata de probar este teorema, es decir, demostrar que el teorema se sigue lógicamente de los axiomas. Veamos un ejemplo clásico. Sean los axiomas: Todos los hombres son falibles. Socrates es un hombre. Un teorema que lógicamente sigue de estos dos axiomas es: Socrates es falible. El primer axioma puede reescribirse como: Para toda X, si X es un hombre, entonces X es falible. El ejemplo puede entonces traducirse a Prolog como sigue: 1 2 falible(X) :- hombre(X). hombre(socrates). y ?- falible(socrates) Yes Un ejemplo más complicado, tomado de la familia de la figura 1.1, es la meta: ?ancestro(tom,pat). Sabemos que progenitor(bob,pat) es un hecho. Podemos derivar entonces que ancestro(bob,pat). Observen que este hecho derivado no puede ser encontrado explı́citamente en nuestro programa sobre la familia, pero puede derivarse a partir de los hechos y reglas en el programa. Un paso en la inferencia de este tipo, puede ser escrito como: progenitor(bob,pat) ⇒ ancestro(bob,pat). El proceso completo de inferencia en dos pasos puede escribirse como: 14 1 Introducción progenitor(bob, pat) ⇒ ancestro(bob, pat) progenitor(tom, bob) ∧ ancestro(bob, pat) ⇒ ancestro(tom, pat) A este tipo de secuencias se les conoce como secuencias de prueba ¿Cómo encuentra Prolog una secuencia de prueba? Prolog encuentra la secuencia de prueba en orden inverso al que acabamos de presentar. En lugar de comenzar con los hechos simples especificados en el programa, Prolog comienza con las metas y, usando reglas, substituye la meta actual por sub-metas, hasta que estas llegan a resolverse por hechos simples. Dada la pregunta: ?- ancestro(tom,pat). Prolog tratará de satisfacer esta meta. Para ello, tratará de encontrar una cláusula en el programa, a partir de la cual la meta dada pueda seguirse lógicamente. Obviamente, las únicas reglas acerca de la relación ancestro/2 son: 1 2 ancestro(X,Z) :progenitor(X,Z). 3 4 5 6 ancestro(X,Z) :progenitor(X,Y), ancestro(Y,Z). Decimos que la cabeza de estas reglas coincide o corresponde (match) con la meta planteada. Las reglas representan formas alternativas en las que Prolog puede resolver la meta. Prolog intentará resolver la pregunta con la primer cláusula que aparece en el programa (lı́neas 1 y 2). Puesto que la meta es ancestro(tom,pat), las variables de la regla pueden ser substituidas conforme a X/tom y Z/pat. La meta original ancestro(tom,pat), es entonces remplazada por la sub-meta progenitor(tom,pat). El paso consistente en usar una regla para transformar una meta en una sub-meta, se muestra gráficamente en la figura 1.5. ancestro(tom, pat) ancestro(X,Z) :- progenitor(X,Z) progenitor(tom, pat) Figura 1.5 El primer paso de la ejecución. La meta de arriba es verdadera si la meta de abajo es verdadera. 1.3 ¿Cómo computa Prolog una solución? 15 Como no hay una cláusula en el programa que coincida con la nueva sub-meta progenitor(tom,pat), la sub-meta falla. Ahora Prolog vuelve atrás (backtrack) para evaluar de forma alternativa su meta original. Ahora intentará la segunda cláusula del programa (lı́neas 4–6). Como antes, las variables de la meta toman los valores: X/tom y Z/pat. Pero Y no toma valor alguno aún. La meta es remplazada por las sub-metas: progenitor(tom,Y), ancestro(Y,pat). La ejecución de este nuevo paso se muestra en la figura 1.6. ancestro(tom,pat) ancestro(X,Z) :progenitor(X,Y), ancestro(Y,Z) ancestro(X,Z) :- progenitor(Z,X) progenitor(tom,pat) progenitor(tom,Y) ancestro(Y,pat) No Figura 1.6 El segundo paso de la ejecución. Dos sub-metas son generadas. Enfrentado ahora el problema de resolver dos sub-metas, Prolog intentará satisfacer la primer sub-meta definida en el programa (¿Porqué?). La primer sub-meta se resuelve fácilmente pues coincide con uno de los hechos del programa. Esto obliga a que Y tome el valor de bob, de forma que la segunda sub-meta se vuelve ancestro(bob,pat). Para satisfacer está sub-meta, Prolog usará nuevamente la primer cláusula del programa (lı́neas 1 y 2). Como en este paso se hace una nueva llamada a esta regla, en realidad Prolog utiliza variables diferentes a la llamada del paso anterior, renombrando las variables como sigue: 1 ancestro(X’,Z’) :- progenitor(X’,Z’). Lo cual conduce a la substitución de variables: X’/bob y Z’/pat. La meta es remplazada por progenitor(bob,pat). Esta meta es satisfecha porque coincide con uno de los hechos del programa. Gráficamente este proceso se muestra en la figura 1.7. Con esta explicación, estudien la siguiente sesión en Prolog: ?- trace. Yes [trace] ?- ancestro(tom,pat). Call: (7) ancestro(tom, pat) ? creep Call: (8) progenitor(tom, pat) ? creep Fail: (8) progenitor(tom, pat) ? creep Redo: (7) ancestro(tom, pat) ? creep Call: (8) progenitor(tom, _L345) ? creep Exit: (8) progenitor(tom, bob) ? creep Call: (8) ancestro(bob, pat) ? creep 16 1 Introducción ancestro(tom,pat) ancestro(X,Z) :progenitor(X,Y), ancestro(Y,Z) ancestro(X,Z) :- progenitor(Z,X) progenitor(tom,pat) progenitor(tom,Y) ancestro(Y,pat) No Y = bob progenitor(tom,bob) ancestro(bob,pat) ancestro(X,Z) :progenitor(Z,X) progenitor(bob,pat) Yes Figura 1.7 El segundo paso de la ejecución. Dos sub-metas son generadas. Call: Exit: Exit: Exit: (9) (9) (8) (7) progenitor(bob, pat) progenitor(bob, pat) ancestro(bob, pat) ? ancestro(tom, pat) ? ? creep ? creep creep creep Yes 1.4. Organización del curso Durante el curso revisaremos tanto el fundamento teórico de la programación lógica, como el uso de Prolog para resolver problemas propios de la inteligencia artificial. La razón de esto debe ser evidente ya: estamos ante una herramienta que es a su vez sujeto de estudio de la IA. Este texto de apoyo esta dividido en dos partes: Fundamentos teóricos y Prolog; sin que esto implique que ambos aspectos serán revisados estrictamente en este orden. Hay un tercer componente que se cubrirá con lecturas complementarias y el desarrollo de un proyecto final: las aplicaciones de la programación lógica. Con respecto a los fundamentos teóricos, iniciaremos con un recordatorio de la lógica de primer orden (capı́tulo 2). Posteriormente revisaremos los conceptos de cláusula y programa definitivos (capı́tulo 3) y el principio de resolución (capı́tulo 4). Continuaremos con el concepto de negación (capı́tulo 5) y cerraremos la primera 1.4 Organización del curso 17 parte del curso con algunas consideraciones sobre el corte y la aritmética (capı́tulo 6). La segunda parte inicia con una introducción menos breve sobre el lenguaje (capı́tulo 7) y continua con una serie de aplicaciones de Prolog a problemas propios de la IA: búsquedas en espacios de soluciones (capı́tulo 8), sistemas expertos (capı́tulo 9), inducción de árboles de decisión (capı́tulo 10), y planeación (capı́tulo 11). De ser posible, concluiremos el curso con algunos aspectos de meta-programación, programación por restricciones y programación de agentes. Parte I Formalismos Capı́tulo 2 Lógica de Primer Orden Resumen En términos generales, la Programación Lógica concierne al uso de la lógica para representar y resolver problemas. Más adelante precisaremos que, en realidad, usaremos una lógica restringida a cláusulas de Horn y la resolución como regla de inferencia [11]. Por ahora, este capı́tulo introduce los conceptos de la lógica de primer orden necesarios para abordar los aspectos formales de la Programación Lógica. Para ello, se adopta un enfoque basado en sistemas formales, que nos permita describir el lenguaje, la teorı́a del modelo y la teorı́a de prueba de la lógica de primer orden. Con este aparato, se introducen los conceptos de unificación y resolución como regla de inferencia. 2.1. Introducción Cuando describimos situaciones de nuestro interés, solemos hacer uso de enunciados declarativos. Decimos que estos enunciados son declarativos en el sentido lingüı́stico del término, esto es, se trata de expresiones del lenguaje natural que son o bien verdaderas, o bien falsas; en contraposición a los enunciados imperativos e interrogativos. La lógica proposicional es declarativa en este sentido, las proposiciones representan hechos que se dan o no en la realidad. La lógica de primer orden tienen un compromiso ontólogico más fuerte [17], donde la realidad implica además, objetos y relaciones entre ellos. Consideren los siguientes ejemplos de enunciado declarativo: 1. Julia es madre y Luis es hijo de Julia. 2. Toda madre ama a sus hijos. donde el enunciado (1) se refiere a los objetos de discurso Julia y Luis, usando propiedades de estos objetos, como ser madre; ası́ como relaciones entre éstos, como hi jo. El enunciado (2) se refiere a relaciones que aplican a todas las madres, en tanto que objetos de discurso. A esto nos referimos cuando hablamos de representación 21 22 2 Lógica de Primer Orden de un problema en el contexto de la Programación Lógica, a describir una situación en términos de objetos y relaciones entre ellos. Si se aplican ciertas reglas de razonamiento a tales representaciones, es posible obtener nuevas conclusiones. Esto concierne a la resolución de problemas en Programación Lógica. Por ejemplo, conociendo (1) y (2) es posible inferir (vı́a Modus Ponens) que: 3. Julia ama a Luis. La idea central de la programación lógica es describir los objetos que conforman un universo de discurso, personas en el ejemplo; ası́ como las relaciones entre ellos, siguiendo con el ejemplo hi jo y madre; y computar tales descripciones para obtener conclusiones como (3). Al describir el problema que queremos resolver, también podemos hacer uso de funciones, relaciones en las cuales sólo hay un valor dada una entrada. Por ejemplo, “madre de” puede representarse como una función (todo hijo tiene una sola madre), pero “hijo de” no. Esto se ilustra en la gráfica 2.1. luis madre de madre de pedro maria juana maria juana hijo de hijo de luis pedro Figura 2.1 La relación madre de es una función; mientras que hijo de no lo es. Como en todo sistema formal, es necesario especificar cuidadosamente la sintaxis de tales enunciados declarativos, es decir, que expresiones pertenecen al lenguaje de la lógica de primer orden, y cuales no; la semántica de estas expresiones, es decir qué hace que una expresión sea verdadera o falsa; ası́ como las reglas de razonamiento que permiten concluir (3) a partir de (1) y (2). Tales cuestiones son el tema de estudio de la lógica matemática. Esta sesión del curso introduce los elementos de la lógica de primer orden, necesarios para abordar la resolución como regla de inferencia en lógica de primer orden y su uso en el lenguaje de programación Prolog. El material aquı́ presentado está basado principalmente en los textos de Michael R. Genesereth y Nils J. Nilsson [5], capı́tulo 2; y el de Ulf Nilsson y Jan Maluszyński [12], capı́tulo 1. Una lectura complementaria a estos textos son los capı́tulos 8 y 9 del texto de Stuart Russell y Peter Norvig [17]. 2.3 El lenguaje de la lógica de primer orden 2.2. 23 Sistemas formales La especificación cuidadosa de la sintaxis y semántica de la lógica de primer orden, se consigue definiendo a ésta última como un sistema formal. Para ello, es necesario considerar tres aspectos: Languaje. Este elemento está asociado a la sintaxis de la lógica de primer orden y de los programas lógicos. El lenguaje de un sistema formal está dado por un conjunto de sı́mbolos conocido como alfabeto y una serie de reglas de construcción o sintácticas. Una expresión es cualquier secuencia de sı́mbolos pertenecientes al alfabeto (primarios). Cualquier expresión es, o no es, una fórmula bien formada (fbf). Las fórmulas bien formadas son las expresiones que pueden formarse con los sı́mbolos del alfabeto a partir de las reglas de construcción y por tanto, pertenecen al languaje de la lógica de primer orden. Teorı́a de modelo. Este elemento está asociado a la semántica de la lógica de primer orden. La teorı́a del modelo establece la interpretación de las fbfs en un sistema formal. Su función es relacionar las fbfs con alguna representación simplificada de la realidad que nos interesa, para establecer cuando una fbf es falsa y cuando verdadera. Esta versión de realidad corresponde a lo que informalmente llamamos “modelo”. Sin embargo, en lógica, el significado de “modelo” está ı́ntimamente relacionado con el lenguaje del sistema formal: si la interpretación M hace que la fbf α 1 sea verdadera, se dice que M es un modelo de α o que M satisface α, y se escribe M |= α. Una fbf es válida si toda interpretación es un modelo para ella. Teorı́a de prueba. Este elemento está asociado con el razonamiento deductivo. La teorı́a de la prueba tiene como objetivo hacer de cada enunciado matemático una fórmula demostrable y rigurosamente deducible. Para ello, la actividad matemática deberı́a quedar reducida a la manipulación de sı́mbolos y sucesiones de sı́mbolos regulada por un conjunto de instrucciones dadas al respecto. La construcción de tal teorı́a implica, además del lenguaje del sistema formal, un subconjunto de fbf que tendrán el papel axiomas en el sistema, y un conjunto de reglas de inferencia que regulen diversas operaciones sobre los axiomas. Las fbf obtenidas mediante la aplicación sucesiva de las reglas de inferencia a partir de los axiomas se conocen como teoremas del sistema. 2.3. El lenguaje de la lógica de primer orden Básicamente, la lógica de primer orden, también conocida como cálculo de predicados, introduce un conjunto de sı́mbolos que nos permiten expresarnos acerca 1 El sı́mbolo α se usa aquı́ como una variable meta-lógica, es decir, una variable que tiene como referente el lenguaje del sistema formal mismo, y por lo tanto, no forma parte del lenguaje del sistema en si. Se usaran letras griegas como variables meta-lógicas. 24 2 Lógica de Primer Orden de los objetos en un dominio de discurso dado. El conjunto de todos estos objetos se conoce como universo de discurso (U ). Los miembros del universo de discurso pueden ser objetos concretos, ej., un libro, un robot, etc; abstractos, ej., números; e incluso, ficticios, ej., unicornios, etc. Un objeto es algo sobre lo cual queremos expresarnos. Como ejemplo, consideren el multi citado mundo de los bloques [5] que se muestra en la figura 2.2. El universo de discurso para tal escenario es el conjunto que incluye los cinco bloques, la el brazo robótico y la mesa: {a, b, c, d, e, brazo, mesa}. Brazo robótico E A D B C Mesa Figura 2.2 El mundo de los bloques, usado para ejemplificar el cálculo de predicados. Una función es un tipo especial de relación entre los objetos del dominio de discurso. Este tipo de relaciones mapea un conjunto de objetos de entrada a un objeto único de salida. Por ejemplo, es posible definir la función parcial sombrero que mapea un bloque al bloque que se encuentra encima de él, si tal bloque existe. Las parejas correspondientes a esta función parcial, dado el escenario mostrado en la figura 2.2 son: {(b, a), (c, d), (d, e)}. El conjunto de todas las funciones consideradas en la conceptualización del mundo se conoce como base funcional. Un segundo tipo de relación sobre los objetos del dominio de discurso son los predicados. Diferentes predicados pueden definirse en el mundo de los bloques, ej., el predicado sobre que se cumple para dos bloques, si y sólo si el primero está inmediatamente encima del segundo. Para la escena mostrada en la figura 2.2, sobre/2 se define por los pares {(a, b), (d, c), (e, d)}. Otro predicado puede ser libre/1, que se cumple para un bloque si y sólo si éste no tiene ningún bloque encima. Este predicado tiene los siguientes elementos {a, e}. El conjunto de todos los predicados usados en la conceptuación se conoce como base relacional. Para universos de discurso finitos, existe un lı́mite superior en el número posible de predicados n-arios que pueden ser definidos. Para un universo de discurso de cardinalidad b (cardinalidad es el número de elementos de un conjunto), existen bn distintas n-tuplas. Cualquier predicado n-ario es un subconjunto de estas bn tuplas. n Por lo tanto, un predicado n-ario debe corresponder a uno de máximo 2(b ) conjuntos posibles. Además de las funciones y predicados, la flexibilidad de la lógica de primer orden resulta del uso de variables y cuantificadores. Las variables, cuyos valores 2.3 El lenguaje de la lógica de primer orden 25 son objetos del universo de discurso, se suelen representar por cualquier secuencia de caracteres que inicie con una mayúscula. El cuantificador “para todo” (∀) nos permite expresar hechos acerca de todos los objetos en el universo del discurso, sin necesidad de enumerarlos. Por ejemplo, toda madre . . . El cuantificador “existe” (∃) nos permite expresar la existencia de un objeto en el universo de discurso con cierta propiedad en partı́cular, por ejemplo, ∃X libre(X) ∧ enLaMesa(X) expresa que hay al menos un objeto que no tiene bloques sobre él y aue se encuentra sobre la mesa. 2.3.1. Sintaxis de la lógica de primer orden Los sı́mbolos primarios de la lógica de primer orden se obtienen al considerar un conjunto numerable de variables, sı́mbolos de predicado y sı́mbolos de funciones. Se asume que los miembros del conjunto Var toman valores en el universo de discurso. Asociado a cada predicado y función, hay un número natural conocido como su aridad, que expresa su número de argumentos. Los predicados de aridad 0 se asumen como variables proposicionales. Las funciones de aridad 0 se asumen como constantes. Considerando los operadores lógicos y los cuantificadores, tenemos que los sı́mbolos primarios o alfabeto del lenguaje de la lógica de primer orden son los que se muestran en la tabla 2.1 Conjunto de constantes: Conjunto de variables: Conjunto de predicados: Conjunto de funciones: Operadores monarios: Operadores binarios: Cuantificadores: Paréntesis: Const Var Pred Func ¬ (negación) ∨ (disyunción) ∀ (cuantificador universal) (, ) Cuadro 2.1 Alfabeto del lenguaje de la lógica de primer orden. El lenguaje del cálculo de predicados LFOL se especifica recursivamente como sigue: Primero definimos un conjunto de términos del lenguaje Term, como la unión de constantes y variables Const ∪ Var; ası́ como la aplicación de las funciones en Func a una secuencia de términos, cuyo tamaño queda determinado por la aridad de la función. Recuerden que las funciones de aridad cero representan constantes. Las siguientes reglas sintácticas expresan que los términos son fbf en el lenguaje: Sintaxis 1 Si α ∈ Const, entonces α ∈ Term Sintaxis 2 Si α ∈ Var, entonces α ∈ Term Sintaxis 3 Si α/n ∈ Func, entonces α(φ1 , . . . , φn ) ∈ Term ssi φi=1,...,n ∈ Term. 26 2 Lógica de Primer Orden Al igual que en el caso de las funciones, la sintaxis de los predicados involucra la aridad del predicado y que sus argumentos sean a su vez términos. Recuerden que los predicados de aridad cero se interpretan como variables proposicionales: Sintaxis 4 Si α/n ∈ Pred, entonces α(φ1 , . . . , φn ) ∈ LFOL ssi φi=1,...,n ∈ Term. La sintaxis de la negación y la disyunción se definen como: Sintaxis 5 Si α ∈ LFOL , entonces ¬α ∈ LFOL Sintaxis 6 Si α ∈ LFOL y β ∈ LFOL , entonces (α ∨ β ) ∈ LFOL La sintaxis del cuantificador universal es como sigue: Sintaxis 7 Si α ∈ LFOL y X ∈ Vars es una variable que ocurre en α, entonces ∀X α ∈ LFOL Las definiciones de la conjunción, la implicación material, la equivalencia material, verdadero y falso, son como en la lógica proposicional: Definición 1 (conjunción) (α ∧ β ) =de f ¬(¬α ∨ ¬β ); Definición 2 (implicación material) (α ⇒ β ) =de f (¬α ∨ β ); Definición 3 (equivalencia material) (α ≡ β ) =de f ((α ⇒ β ) ∧ (β ⇒ α)); Definición 4 (falso) f =de f ¬α ∧ α; Definición 5 (verdadero) t =de f ¬f La definición del cuantificador existencial es la siguiente: Definición 6 (cuantificador existencial) ∃X α =de f ¬(∀X ¬α) Siendo estrictos, el cuantificador propiamente dicho, es el sı́mbolo de cuantificador seguido de una variable, puesto que ∀X y ∀Y tienen significados diferentes. En una fbf de la forma ∀X α, se dice que la fbf α está en el alcance del cuantificador ∀X. En tal caso, se dice que la ocurrencia de X en α está acotada, en caso contrario se dice que la ocurrencia de la variable es libre. Por ejemplo, en ∀X sobre(X,Y ) la variable X está acotada, mientras que Y está libre. Un término sin variables se conoce como término de base. 2.4. La semántica de la lógica de primer orden Antes de introducir las definiciones formales de la semántica de la lógica de primer orden, consideremos algunas expresiones posibles en está lógica, usando como ejemplo el mundo de los bloques (Figura 2.2). Si queremos expresar que al menos algún bloque no tiene nada encima, podemos usar los predicados bloque/1 y libre/1 en la siguiente expresión: ∃X bloque(X) ∧ libre(X). Esta fbf expresa que existe un 2.4 La semántica de la lógica de primer orden 27 X tal que X es un bloque y X está libre (no tiene otro bloque encima). Observen que cuando usamos cuantificadores, siempre tenemos en mente el universo de discurso en cuestión o dominio. El dominio puede especificarse en término de conjuntos. Luego, si el dominio D es el conjunto de constantes {a, b, c, d, e, brazo, mesa}, podemos decir que B ⊂ D = {a, b, c, d, e} es el conjunto de bloques en D. Entonces, es posible plantear una expresión equivalente a ∃X bloque(X) ∧ libre(X), usando la fbf ∀X libre(x), si especificamos que libre/1 tiene como dominio B. Una interpretación del predicado libre/1 es un subconjunto de B tal que si un bloque está libre, pertenece a este subconjunto. Para un predicado de aridad dos, como sobre/2 cuyo dominio son los bloques B × B, podemos decir que su interpretación es un subconjunto de B × B. En general, para un predicado de aridad n, su interpretación es un subconjunto en Dn . 2.4.1. Teorı́a de modelo de la lógica de primer orden Para obtener un modelo para el lenguaje LFOL formamos el par M = hD,V i, donde D es el universo de discurso, ej. cualquier colección de objetos sobre la que queremos expresarnos, y la interpretación V es una función, tal que: Para cualquier predicado α de aridad n, V (α) regresa las n-tuplas que corresponden a la interpretación del predicado. En el ejemplo, siguiendo nuevamente la figura 2.2, consideren el predicado sobre/2. Su interpretación es un subconjunto de D2 = D × D. Para la escena mostrada, V (sobre) = {(a, b), (e, d), (d, c)}. Para una constante, la función V regresa la misma constante, ej. V (a) = a. Algunas veces la expresión V (α) se abrevia α V . Una posible interpretación V para la escena del mundo de los bloques mostrada en al figura 2.2, es: aV = a bV = b cV = c dV = d eV = e sobreV = {(a, b), (e, d), (d, c)} enLaMesaV = {b, c} libreV = {a, e} porEncimaV = {(a, b), (e, d), (e, c), (d, c)} Todo esto puede especificarse formalmente con la siguiente definición: 28 2 Lógica de Primer Orden Definición 7 (Interpretación) Una interpretación V , con respecto a un dominio de discurso D, es una función que satisface las siguientes propiedades: i) Si α ∈ Const, Entonces V (α) = α; ii) Si α/n ∈ Pred, Entonces V (α) ⊆ Dn . Observen que las variables no están incluidas en la interpretación. Interpretar las variables de manera independiente a otros sı́mbolos en el lenguaje, es una práctica aceptada. Decimos que U es una asignación de variables basada en el modelo M = hD,V i si para todo α ∈ Var, U(α) ∈ Term. Por ejemplo, en el mundo de los bloques X U = a, es una asignación de variables. Esta abreviatura a veces se expande como U = {X\a} y se conoce como substitución. Una interpretación V y una asignación de variables U pueden combinarse en una asignación conjunta TVU que aplica a los términos de primer orden en general. La asignación de términos T dadas la interpretación V y la asignación de variables U, es un mapeo de términos a objetos del universo de discurso que se define como sigue: Semántica 1 Si α ∈ Const, entonces TVU (α) = V (α). Semántica 2 Si α ∈ Var, entonces TVU (α) = U(α). Semántica 3 Si α ∈ Term y es de la forma α(φ1 , . . . , φn ); y V (α) = g; y TVU (φi ) = xi , entonces TVU (α(φ1 , . . . , φn )) = g(x1 , . . . , xn ). El concepto de satisfacción guarda una relación importante con las interpretaciones y las asignaciones. Por convención, el hecho de que el enunciado α sea satisfecho bajo una interpretación V y una asignación U, se escribe: |=V α[U] Entonces podemos escribir M |= VU (α) para expresar que α es verdadera en el modelo M = hD,V i cuando las variables en α toman valores de acuerdo a la asignación U. Por ejemplo, M |= VU (sobre(X, b)) si X\a ∈ U. En realidad, la noción de satisfacción varı́a dependiendo de la clase del enunciado α. Ası́ tenemos que una interpretación V y una asignación de variables U satisfacen una ecuación, si y sólo si la correspondiente asignación de términos TVU mapea los términos igualados a un mismo objeto. Cuando este es el caso, los términos se dicen correferenciados: Semántica 4 M |=V (α = β )[U] ssi TVU (α) = TVU (β ). Para el caso de un enunciado atómico que no sea una ecuación, la satisfacción se cumple si y sólo si la tupla formada por los objetos designados por los términos en el enunciado, es un elemento de la relación designada por la relación constante: Semántica 5 M |=V α(τ1 , . . . , τn )[U] ssi (TVU (τ1 ), . . . , TVU (τn )) ∈ V (α) . Consideren como ejemplo la interpretación V definida para el mundo de los boques. Puesto que la constante a designa al bloque a y la constante b al bloque b, y 2.5 Inferencia en la lógica de primer orden 29 el par ordenado (a, b) es miembro del conjunto que interpreta la relación sobre, entonces es el caso que |=V sobre(a, b)[U], por lo cual podemos decir que sobre(a, b) es verdadera en esa intepretación. Evidentemente: Semántica 6 M |=V ¬(α)[U] ssi M 6|=V α[U]. y: Semántica 7 M |=V (α ∨ β )[U] ssi M |=V α[U] ó M |= β [U]. Un enunciado cuantificado universalmente se satisface, si y sólo si el enunciado bajo el alcance del cuantificador, se satisface para todas las asignaciones posibles de la variable cuantificada. Un enunciado cuantificado existencialmente se satisface, si y sólo si el enunciado bajo el alcance del cuantificador es satisfecho por una asignación de variables. Semántica 8 M |=V ∀X α[U], ssi para toda β en el universo de discurso, es el caso que M |=V α[U 0 ], donde U 0 (X) = β y U 0 (γ) = U(γ) para toda γ 6= X. Debido a la última condición en esta regla, se dice que U 0 es una asignación Xalternativa a U. La regla semántica también puede leerse como: M |=V ∀X α[U] si para toda asignación de variables X-alternativa U 0 , M |=V α[U 0 ]. Si una interpretación V safisface a un enunciado α para toda asignación de variables, se dice que V es un modelo de α. Un enunciado se dice satisfacible si existe alguna interpretación y asignación de variables que lo satisfaga. De otra forma, se dice que el enunciado es insatisfacible. Una fbf α es válida si y sólo si se satisface en toda intepretación y asignación de variables. Las fbf válidas lo son en virtud de su estructura lógica, por lo que no proveen información acerca del dominio descrito. Por ejemplo p(X) ∨ ¬p(X) es una fbf válida. 2.5. Inferencia en la lógica de primer orden Volvamos al ejemplo de la introducción: 1. Toda madre ama a sus hijos. 2. Julia es madre y Luis es hijo de Julia. Conociendo (1) y (2) es posible concluir que: 3. Julia ama a Luis. Podemos formalizar este ejemplo en Lógica de Primer Orden como sigue: 1. ∀X ∀Y madre(X) ∧ hi jo de(Y, X) ⇒ ama(X,Y ) 2. madre( julia) ∧ hi jo de(luis, julia) 30 2 Lógica de Primer Orden 3. ama( julia, luis) Una vez que hemos formalizado nuestros enunciados, el proceso de inferencia puede verse como un proceso de manipulación de fbf, donde a partir de formulas como (1) y (2), llamadas premisas, se produce la nueva fbf (3) llamada conclusión. Estas manipulaciones se pueden formalizar mediante reglas de inferencia. Entre las reglas de inferencia de la lógica de primer orden encontramos: Modus Ponens. O regla de eliminación de la implicación. Esta regla dice que siempre que las fbfs de la forma α y α ⇒ β pertenezcan a las premisas o sean concluidas a partir de ellas, podemos inferir β : α α ⇒β (⇒ E) β Eliminación de cuantificador universal. Esta regla expresa que siempre que una fbf de la forma ∀Xα pertenezca a las premisas o sea concluida a partir de ellas, una nueva fbf puede ser concluida al remplazar todas las ocurrencias libres de X en α por algún término t que es libre con respecto a X (todas las variables en t quedan libres al substituir X por t. La regla se presenta como sigue: ∀Xα(X) (∀E) α(t) Introducción de conjunción. Cuando las fbf α y β pertenezcan a las premisas o sean concluidas a partir de ellas, podemos inferir α ∧ β : α β (∧I) α ∧β La correctez de estas reglas puede ser demostrada directamente a partir de la definición de la semántica de las fbf en LFOL . El uso de las reglas de inferencia puede ilustrarse con el ejemplo formalizado. Las premisas son: 1. ∀X∀Y madre(X) ∧ hi jo de(Y, X) ⇒ ama(X,Y ) 2. madre( julia) ∧ hi jo de(luis, julia) Al aplicar la eliminación de cuantificador universal (∀E) a (1) obtenemos: 3. ∀Y (madre( julia) ∧ hi jo de(Y, julia) ⇒ ama( julia,Y ) Al aplicar nuevamente (∀E) a (3) obtenemos: 4. madre( julia) ∧ hi jo de(luis, julia) ⇒ ama( julia, luis) Finalmente, al aplicar Modus Ponens a (2) y (4): 5. ama( julia, luis) 2.6 Substituciones 31 La conclusión (5) ha sido obtenida rigurosamente, aplicando las reglas de inferencia. Esto ilustra el concepto de derivación. El hecho de que una formula α sea derivable a partir de un conjunto de fórmulas ∆ se escribe ∆ ` α. Si las reglas de inferencia son consistentes (sound), siempre que ∆ ` α entonces ∆ |= α. Esto es, si nuestra lógica es consistente, cualquier fbf que puede ser derivada de otra fbf, es tambien una consecuencia lógica de ésta última. Definición 8 (Consistencia y completitud) Un conjunto de reglas de inferencia se dice consistente si, para todo conjunto de fbf cerradas (sin ocurrencia de variables libres) ∆ y cada fbf cerrada α, siempre que ∆ ` α se tiene que ∆ |= α. Las reglas de inferencia se dicen completas si ∆ ` α siempre que ∆ |= α. 2.6. Substituciones Formalmente, como ya se mencionó, una substitución es un mapeo de las variables del lenguaje a los términos del mismo: Definición 9 (Substitución) Una substitución es un conjunto finito de pares de la forma {X1 /t1 , . . . , Xn /tn } donde cada tn es un término y cada Xn es una variable, tal que Xi 6= ti y Xi 6= X j si i 6= j. La substitución vacı́a se denota por ε. Asumamos que Dom({X1 /t1 , . . . , Xn /tn }) denota al conjunto {X1 , . . . , Xn }, también conocido como dominio; y Range({X1 /t1 , . . . , Xn /tn }) denota al conjunto {t1 , . . . ,tn }, también conocido como rango. Entonces la regla anterior expresa que las variables en el dominio de una substitución son únicas y no incluyen la substitución de la variable por si misma. La aplicación Xθ de la substitución θ a la variable X se define como: t Si X/t ∈ θ Xθ = X En otro caso observen que para las variables no incluidas en Dom(θ ), θ aparece como la función identidad. Es importante extener el concepto de substitución a las fbf: Definición 10 (Aplicación) Sea θ una substitución {X1 /t1 , . . . , Xn /tn } y α una fbf. La aplicación αθ es la fbf obtenida al remplazar simultáneamente ti por toda ocurrencia de Xi en α (1 ≤ i ≤ n). αθ se conoce como un caso (instance) de α. Ejemplos: ama(X,Y ) ∧ madre(X){X/ julia,Y /luis} = ama( julia, luis) ∧ madre( julia) p( f (X, Z), f (Y, a)) {X/a,Y /Z,W /b} = p( f (a, Z), f (Z, a)) p(X,Y ) {X/ f (Y ),Y /b} = p( f (Y ), b) 32 2 Lógica de Primer Orden Definición 11 (Composición) Sean θ y σ dos substituciones de la forma: θ = {X1 /s1 , . . . Xm /sm }σ = {Y1 /t1 , . . .Yn /tn } La composición θ σ se obtiene a partir del conjunto: {X1 /s1 σ , . . . Xm /sm σ ,Y1 /t1 , . . .Yn /tn } de la manera siguiente: eliminar todas las Xi /si σ para las que Xi = si σ (1 ≤ i ≤ m) y eliminar también aquellas Y j /t j para las cuales Y j ∈ Dom(θ ) (1 ≤ j ≤ n). Por ejemplo: {X/ f (Z),Y /W }{X/a, Z/a,W /Y } = {X/ f (a), Z/a,W /Y } Definición 12 (Substitución idempotente) Una substitución θ se dice idempotente si θ = θ θ . Se puede probar que una substitución θ es idempotente si y sólo si Dom(θ ) ∩ Range(θ ) = 0, / es decir si el dominio y el rango de la substitución son disjuntos. Otras propiedades de las substituciones son: Definición 13 (Propiedades de las substituciones) Sean θ , α y β substituciones y sea F una fbf. Entonces: E(θ α) = (Eθ )α (θ α)β = θ (αβ ) εθ = θ ε = θ Observen que, aunque las substituciones son asociativas, éstas no son conmutativas. Las substituciones son importantes para definir una regla de inferencia de especial relevancia para nosotros, conocida como la regla de resolución. Con las definiciones introducidas en este capı́tulo podemos abordar el tema de los programas lógicos definitivos. Capı́tulo 3 Cláusulas y Programas Definitivos Resumen La idea central de la programación lógica es usar la computadora para obtener conclusiones a partir de descripciones declarativas, como las introducidas en el capı́tulo anterior. Estas descripciones, llamadas programas lógicos, consisten en un conjunto finito de fórmulas bien formadas (fbfs) de la lógica de primer orden. La idea central tiene sus raı́ces en la demostración automática de teoremas, sin embargo, pasar de la demostración automática de teoremas experimental a la programación lógica aplicada, requiere mejoras con respecto a la eficiencia del sistema propuesto. Tales mejoras se logran imponiendo restricciones sobre las fbfs del lenguaje utilizado, de forma que podamos usar una poderosa regla de inferencia conocida como principio de resolución-SLD. Este capı́tulo introduce el concepto de cláusula y programa lógico definitivos. Más adelante se introducirá el concepto menos restrictivo de programas generales, pero el paso por los programas definitivos es necesario para comprender las bases teóricas de Prolog. El aparato técnico aquı́ presentado se basa principalmente en el texto de Nilsson et al. [12]. 3.1. Cláusulas definitivas Consideremos una clase especial de enunciados declarativos del lenguaje natural, que utilizamos para describir hechos y reglas positivos. Un enunciado de este tipo puede especificar: Que una relación se mantiene entre elementos del universo de discurso (hechos). Que una relación se mantiene entre elementos del universo de discurso, si otras relaciones se mantienen (reglas). Consideren los siguientes enunciados en lenguaje natural: 1. Antonio es hijo de Juan. 2. Ana es hija de Antonio. 3. Juan es hijo de Marcos. 33 34 3 Cláusulas y Programas Definitivos 4. Alicia es hija de Juan. 5. El nieto de una persona es el hijo del hijo de esa persona. Estos enunciados pueden formalizarse en dos pasos. Primero, procedemos con las fbf atómicas que describen hechos: 1. 2. 3. 4. hijo hijo hijo hijo de(antonio, juan) de(ana,antonio) de(juan,marcos) de(alicia,juan) El último enunciado puede aproximarse como: Para toda X e Y , X es nieto de Y si existe alguna Z tal que Z es hijo de Y y X es hijo de Z. En lógica de primer orden, esto se escribirı́a (observen que la implicación está invertida (←) a la usanza de Prolog): ∀X∀Y (nieto de(X,Y ) ← ∃Z(hi jo de(Z,Y ) ∧ hi jo de(X, Z))) Usando las equivalencias de la lógica de primer orden (en particular α ⇒ β ≡ ¬α ∨ β ; y la equivalencia entre cuantificadores ∀Xα ≡ ¬∃X¬α), esta fbf puede escribirse de diversas maneras: ∀X∀Y (nieto de(X,Y ) ∨ ¬∃Z(hi jo de(Z,Y ) ∧ hi jo de(X, Z))) ∀X∀Y (nieto de(X,Y ) ∨ ∀Z¬(hi jo de(Z,Y ) ∧ hi jo de(X, Z))) ∀X∀Y ∀Z(nieto de(X,Y ) ∨ ¬(hi jo de(Z,Y ) ∧ hi jo de(X, Z))) ∀X∀Y ∀Z(nieto de(X,Y ) ← (hi jo de(Z,Y ) ∧ hi jo de(X, Z))) Observen que estas fbf están cerradas (no contienen variables fuera del alcance de los cuantificadores) bajo el cuantificador universal. Además, la regla tiene la siguiente estructura: α0 ← α1 ∧ · · · ∧ αn (n ≥ 0) Los bloques de construcción αi de estas fbf, se conocen como literales. Definición 14 (Literal) Una literal es un átomo o la negación de un átomo. Una literal positiva es un átomo. Una literal negativa es la negación de un átomo. Un ejemplo de literal positiva es hi jo de( juan, marcos). Un ejemplo de literal negativa es ¬hi jo de( juan, alicia). Si p y q son predicados y f es un functor, entonces p(X, alicia) y q(Y ) son literales positivas. ¬q(alicia, f (Y )) es una literal negativa. Definición 15 (Cláusula) Una cláusula es una disyunción finita de cero o más literales. 3.2 Programas definitivos y Metas 35 Definición 16 (Cláusula definitiva) Una cláusula se dice definitiva, si tiene exactamente una literal positiva. α0 ∨ ¬α1 ∨ · · · ∨ ¬αn (n ≥ 0) lo cual es equivalente a la forma general de fbf que nos interesaba: α0 ← α1 ∧ · · · ∧ αn (n ≥ 0) Si n = 0 tenemos por definición que la literal α0 será una literal positiva, por lo que la cláusula definitiva toma la forma de un hecho. El cuerpo vacı́o puede representarse por el conectivo nulo , que es verdadero en toda interpretación (por simetrı́a también se asume un conectivo nulo 2, que es falso en toda interpretación). Si n > 0 la cláusula definitiva toma la forma de una regla, donde α0 se conoce como cabeza de la regla; y la conjunción α1 ∧ · · · ∧ αn se conoce como cuerpo de la regla. El ejemplo de la relación nieto de/2 y la regla que lo define, muestra que las cláusulas definitivas usan una forma restringida de cuantificación existencial, las variables que ocurren sólo en el cuerpo de la cláusula están cuantificadas existencialmente en el cuerpo de la cláusula (el mismo ejemplo muestra que esto equivale a que tales variables estén cuantificadas universalmente sobre toda la fbf). 3.2. Programas definitivos y Metas La definición de programa definitivo es ahora directa: Definición 17 (Programa definitivo) Un programa definitivo es un conjunto finito de cláusulas definitivas. Si una cláusula tiene sólo literales negativas, estamos hablando de una meta definitiva: Definición 18 (Meta definitiva) Una cláusula sin literales positivas es una meta definitiva. ← α1 ∧ · · · ∧ αn (n ≥ 1) Definición 19 (Cláusula de Horn) Una cláusula de Horn es una cláusula definitva ó una meta definitiva. Observen que a partir de estas definiciones, la cláusula vacı́a 2 1 es una meta definitiva y, por lo tanto, una cláusula de Horn. Adoptar a las cláusulas de Horn para abordar los programas y metas definitivos, constituye una restricción. Por ejemplo, no podemos expresar p(a) ∨ p(b). Esta perdida en expresividad se ve compensada por la ganancia en tratabilidad. Debido a su estructura restringida, las cláusulas de Horn son más fáciles de manipular que 1 En realidad, la cláusula vacı́a tiene la forma 2 ← que equivale a 2. 36 3 Cláusulas y Programas Definitivos las cláusulas generales. En particular, esto es cierto para la deducción basada en resolución-SLD, que resulta completa para las cláusulas de Horn. El significado lógico de las metas puede explicarse haciéndo referencia a la fbf equivalente cuantificada universalmente: ∀X1 . . . Xm ¬(α1 ∧ · · · ∧ αn ) donde las Xi son todas variables que ocurren en la meta. Esto es equivalente a: ¬∃X1 . . . Xm (α1 ∧ · · · ∧ αn ) Esto puede verse como una pregunta existencial que el sistema tratará de negar, mediante la construcción de un contra ejemplo. Esto es, el sistema tratará de encontrar términos ti . . .tm tales que las fbf obtenidas a partir de α1 ∧ · · · ∧ αm al remplazar la variable Xi por ti (1 ≤ i ≤ m) son verdaderas en todo modelo del programa. Es decir, el sistema construirá una consecuencia lógica del programa que es un caso de la conjunción de todas las submetas de la meta. Al dar una meta definitiva, el usuario selecciona un conjunto de conclusiones a ser construı́das. Este conjunto puede ser finito o infinito. El problema de como construir tal conjunto lo veremos al tratar la resolución SLD. Ejemplo 1 Tomando en cuenta los hechos y reglas sobre una familia presentados al principio de esta sesión, el usuario podrı́a estar interesado en las siguientes consultas (se muestra también la meta definitiva correspondiente): Consulta Meta definitiva ¿Es Ana hija de Antonio? ← hi jo(ana, antonio) ¿Quién es nieto de Ana? ← nieto(X, ana) ¿De quién es nieto Antonio? ← nieto(antonio, X) ¿Quién es nieto de quién? ← nieto(X,Y ) Las respuestas obtenidas serı́an: Puesto que la primer meta no contiene variables, la respuesta serı́a Si (Yes). Puesto que el programa no contiene información sobre los nietos de Ana, la respueta a la segunda consulta es No (o ninguno). Puesto que Antonio es nieto de Marcos, la respuesta obtenida serı́a X = marcos. La consulta final obtiene tres respuestas: X = antonio Y = alicia, X = alicia Y = marcos, X = ana Y = juan. Es posible hacer consultas más elaboradas como ¿Hay alguna persona cuyos nietos son Antonio y Alicia? ← nieto(antonio, X) ∧ nieto(alicia, X) cuya respuesta esperada es X = marcos. 3.3 El modelo mı́nimo de Herbrand 3.3. 37 El modelo mı́nimo de Herbrand Los programas definitivos solo pueden expresar conocimiento positivo, tanto los hechos, como las reglas, nos dicen que elementos de una estructura están en una relación, pero no nos dicen cuales no. Por lo tanto, al usar el lenguaje de los programas definitivos, no es posible construir descripciones contradictorias, es decir, conjuntos de fbf no satisfacibles. En otras palabras, todo programa definitivo tiene un modelo. Recordemos que una interpretación que hace verdadera una fbf es su modelo: Definición 20 (Modelo) Sea α una fbf y V una interpretación. V es un modelo de α si |=V α. Definición 21 Sea ∆ un conjunto finito de fbf y V una interpretación. V es un modelo de ∆ si |=V α para toda α ∈ ∆ . Existe una clase interesante de interpretaciones, llamadas de Herbrand en honor del francés Jacques Herbrand. En esta sección estudiaremos algunas propiedades de los modelos de Herbrand que explican porque son útiles y necesarios en el contexto de la programación lógica. Además, los modelos de Herbrand proveen una semántica natural para los programas definitivos. Comenzaremos definiendo el Universo y la Base de Herbrand: Definición 22 (Universo y Base de Herbrand) Sea L un alfabeto de primer orden que contiene al menos un sı́mbolo de constante (|Const| ≥ 1). El Universo de Herbrand UL es el conjunto de todos los términos formados con las constantes y functores de L. La Base de Herbrand BL es el conjunto de todos los átomos que pueden formarse con los predicados y los términos en el Universo de Herbrand UL . El universo y la base de Herbrand se definen normalmente para un programa dado. En ese caso, se asume que el alfabeto L consiste exactamente de aquellos sı́mbolos que aparecen en el programa. Se asume también que el programa tiene al menos una constante (de otra forma el dominio estarı́a vacı́o). Ejemplo 2 Consideren el siguiente programa definitivo ∆ = {impar(s(0)), impar(s(s(X))) ← impar(X)} Si restringimos el lenguaje L a los sı́mbolos que aparecen en este programa definitivo, tenemos que el universo de Herbrand es: UL = {0, s(0), s(s(0)), s(s(s(0))), . . . } Puesto que el programa sólo incluye al predicado impar, la base de Herbrand se define como: BL = {impar(0), impar(s(0)), impar(s(s(0))), . . . } 38 3 Cláusulas y Programas Definitivos Ejemplo 3 Consideren este otro programa ∆ = {p(a), q(a, f (b), q(X, X) ← p(X)}. Sea L es lenguaje de primer orden dado por los sı́mbolos en ∆ . El Universo de Herbrand UL es el conjunto infinito: UL = {a, b, f (a), f (b), f ( f (a)), f ( f (b)), . . . } Y la base de Herbrand es: BL = {p(a), p(b), q(a, b), p( f (a)), p( f (b)), q(a, f (a)), q(a, f (b)), . . . } Lo que hace especial a una intrepretación de Herbrand es que se toma el conjunto de todos los términos sin variables (UL ) como el dominio de la interpretación. El mapeo de los términos a los elementos del dominio es tal que, cada término sin variables es mapeado al elementos correspondiente en el dominio. De forma que cada término sin variables en el lenguaje, se refiere a si mismo en el dominio. Definición 23 (Interpretación de Herbrand) Sea L un lenguaje de primer orden. V es una interpretación de Herbrand de L si y sólo si: El dominio de V es UL . Para cada constance c ∈ L, V (c) = c. Para cada functor f /n ∈ L, se tiene un mapeo V ( f ) de ULn a UL definido por V ( f )(t1 , . . . ,tn ) = f (t1 , . . . ,tn ). Para cada predicado p/n ∈ L, V (p) ⊆ ULn . La función J f mapea t1 , . . . ,tn al término f (t1 , . . . ,tn ) en el Universo de Herbrand UL . Definición 24 (modelo de Herbrand) Sea L un lenguaje de primer orden, ∆ un conjunto de fbf en L, y V una interpretación de Herbrand de L. Si V es un modelo de ∆ , se dice que es un modelo de Herbrand de ∆ . Observen que una interpretación de Herbrand V está completamente especificada por el conjunto de todas las α ∈ BL que son verdaderas bajo V . Podemos por lo tanto representar cualquier interpretación de Herbrand economicamente como un subconjunto (denotado por también V ) de BL . En otras palabras, una interpretación de Herbrand, es un subconjunto de la Base de Herbrand. Ejemplo 4 Consideren el programa ∆ en el ejemplo 2. Una posible interpretación de este programa es imparV = {hs(0)i, hs(s(s(0)))i}. Una intepretación de Herbrand se puede especificar mediante una familia de tales relaciones (una por cada sı́mbolo de predicado). Ejemplo 5 Consideren ahora algunas interpretaciones de Herbrand de ∆ tal y como se definio en el ejemplo 3: 3.3 El modelo mı́nimo de Herbrand 39 V1 = {p(a), p(b), q(a, b, ), q(b, b)} V2 = {p(a), q(a, a), q(a, f (b))} V3 = {p( f ( f (a))), p(b), q(a, a), q(a, f (b))} V4 = {p(a), p(b), q(a, a), q(b, b), q(a, f (b))} V2 y V4 son modelos de Herbrand de ∆ = {p(a), q(a, f (b), q(X, X) ← p(X)}. V1 y V3 no lo son. 3.3.1. Resultados concernientes a los modelos de Herbrand Las interpretaciones y los modelos de Herbrand tienen dos propiedades atractivas. La primera es pragmática: para poder determinar si una interpretación de Herbrand V es un modelo de una fbf cuantificada universalmente ∀α, es suficiente verificar si α es verdadera en V , para todas las asignaciones posibles de las variables de α. La segunda razón para considerar las interpretaciones de Herbrand es más teórica. Para el lenguaje restringido de cláusulas definitivas, si queremos verificar que una fbf atómica α es consecuencia de un programa definitivo ∆ basta con verificar que todo modelo de Herbrand de ∆ es también un modelo de Herbrand de α. Para entrar en detalles, es necesaria la siguiente proposición: Proposición 1 Sea ∆ un conjunto de cláusulas en un lenguaje de primer orden L. Entonces ∆ tiene un modelo, si y sólo si ∆ tiene un modelo de Herbrand. La prueba de esta proposición es como sigue: Supongamos que ∆ tiene un modelo M. Si definimos una interpretación V de Herbrand tal que V (α(t1 , . . . ,tn )) define sólo aquellas extensiones de α que son válidas en M), tenemos que V es un modelo de Herbrand de ∆ . Dicho de otra forma, un modelo de Herbrand, es un modelo. Observen que esta proposición es verdadera sólo para conjuntos de cláusulas. Por ejemplo, consideren a L como un lenguaje de primer orden formado por los sı́mbolos en ∆ = {∃X p(X), ¬p(a)}. Claramente ∆ tiene un modelo, pero este no es un modelo de Herbrand. La razón es el dominio de la interpretación. En una interpretación de Herbrand, el dominio es UL = {a}, y necesitamos dos constantes al menos en UL para construir un modelo de ∆ . Hemos mencionado la importancia del concepto de implicación lógica (o consecuencia lógica). Es común que a partir de un conjunto ∆ y una fbf γ, queremos encontrar si ∆ |= γ. Esto es cierto si cada modelo de ∆ es también un modelo de γ. Lo interesante viene ahora: Proposición 2 Sea ∆ un conjunto de fbf y α una fbf. Sea S = ∆ ∪ {¬α}. Entonces ∆ |= α si y sólo si S no tiene modelo de Herbrand. 40 3 Cláusulas y Programas Definitivos La prueba de esta proposición es como sigue: ∆ |= α si y sólo si ∆ ∪ {¬α} no es satisfacible. Esto es, si S es no satisfacible, lo cual es cierto sólo si S no tiene modelos y por lo tanto, no tiene modelo de Herbrand. Lo que esta proposición nos dice es que si queremos probar que ∆ |= α, sólo debemos considerar modelos de Herbrand de la forma S. Aunque el número de interpretaciones de Herbrand es normalmente infinito, la tarea de investigar interpretaciones de Herbrand es más tratable que la de investigar cualquier interpretación arbitraria, puesto que nos restringimos a un dominio único definitivo por el Universo de Herbrand UL . Observen que la base de Herbrand de un programa definitivo ∆ es siempre un modelo de Herbrand del programa. Sin embargo, es un modelo nada interesante, esto es, cada predicado n-ario en el programa es interpretado como la relación naria completa sobre el dominio de los terminos cerrados. ¿Qué es lo que hace a un modelo de programa interesante? En lo que sigue demostraremos la existencia de un modelo mı́nimo único, llamado el modelo mı́nimo de Herbrand de un programa definitivo. Luego mostraremos que este modelo contiene toda la información positiva presente en el programa. Los modelos de Herbrand de un programa definitivo son subconjuntos de su base de Herbrand. Por lo tanto, la inclusión en conjuntos establece un orden natural en tales modelos. Para poder demostrar la existencia de modelos mı́nimos con respecto a la inclusión es suficiente demostrar que la intersección de todos los modelos de Herbrand es también un modelo de Herbrand. Teorema 1 (Intersección de modelos) Sea M una familia no vacı́a de modelos de T Herbrand de un programa definitivo ∆ . Entonces la intersección V = M es un modelo de Herbrand de ∆ . La demostración es como sigue: Supongamos que V no es un modelo de ∆ . Por lo tanto existe una una cláusula sin variables en ∆ , de la forma: α0 ← α1 , . . . , αn (n ≥ 0) que no es verdera en V . Esto ı́mplica que V contiene a α1 , . . . αn , pero no a α0 . Luego, α1 , . . . , αn son miembros de toda interpretación en la familia M. Más importante aún, debe existir un modelo Vi ∈ M tal que α0 6∈ Vi , de forma que la cláusula α0 ← α1 , . . . , αn (n ≥ 0) no es verdadera en ese Vi . Por lo tanto Vi no es un modelo del programa ∆ , lo que contradice nuestro supuesto. Al tomar la intersección de todos los modelos de Herbrand (se sabe que todo programa definitivo tiene un modelo de Herbrand BL ) de un programa definitivo, obtenemos el modelo mı́nimo de Herbrand el programa. Ejemplo 6 Sea ∆ el programa definitivo {masculino(adan), f emenino(eva)} con su interpretación obvia. ∆ tiene los siguientes modelos de Herbrand: {masculino(adan), f emenino(eva)} {masculino(adan), masculino(eva), f emenino(eva)} 3.3 El modelo mı́nimo de Herbrand 41 {masculino(adan), masculino(eva), f emenino(adan)} {masculino(adan), masculino(eva), f emenino(eva), f emenino(adan)} No es complicado confirmar que la intersección de estos modelos produce un modelo de Herbrand. Sin embargo, todos los modelos salvo el primero, contienen átomos incompatibles con el significado esperado del programa. Observen también que la intersección de todos los modelos nos lleva a un modelo que corresponde con el significado esperado. Este ejemplo nos muestra la conexión entre los modelos mı́nimos de Herbrand y el modelo intentado de un programa definitivo. Este modelo es una abstracción del mundo a ser descrita por el programa. El mundo puede ser más rico que el modelo mı́nimo de Herbrand. Por ejemplo hay más f ememinos que eva. Sin embargo, aquella información que no se provea explı́citamente (hechos) o implı́citamente (reglas) no puede ser obtenida como respuesta a una meta. Las respuestas corresponden a las consecuencias lógicas del programa. Teorema 2 El modelo mı́nimo de Herbrand M∆ de un programa definitivo ∆ es el conjunto de todas las consecuencias lógicas atómicas de base del programa. Esto es: M∆ = {α ∈ B∆ |∆ |= α}. La prueba de este teorema pasa por demostrar que M∆ ⊇ {α ∈ BL |∆ |= α} y que M∆ ⊆ {α ∈ B∆ |∆ |= α}. 3.3.2. Construcción del modelo mı́nimo de Herbrand La pregunta que emerge es ¿Cómo podemos construir el modelo mı́nimo de Herbrand? o ¿Cómo puede aproximarse sucesivamente por medio de la enumeración de sus elementos? La respuesta a estas preguntas se da mediante una aproximación de punto fijo (el punto fijo de una función f : D → D es un elemento x ∈ D tal que f (x) = x) a la semántica de los programas definitivos. Un programa definitivo está compuesto de hechos y reglas. Es evidente que todos los hechos deben incluirse en cualquier modelo de Herbrand. Si la interpretación V no incluye el hecho α del programa ∆ , entonces V no es un modelo de Herbrand de ∆. Ahora consideremos una regla de la forma α0 ← α1 , . . . , αn (n > 0). La regla especifica que siempre que α1 , . . . , αn son verdaderas, también lo es α0 . Esto es, tomando cualquier asignacion de valores θ que haga que la regla no tenga variables sin valor (α0 ← α1 , . . . , αn )θ : Si la interpretación V incluye a α1 θ , . . . αn θ , deberá incluir también a α0 θ para ser un modelo. Consideren ahora el conjunto V1 de todos los hechos sin variables de el programa. Es posible utilizar cada regla para aumentar V1 con nuevos elementos que necesariamente pertenencen a todo modelo. De modo que se obtiene un nuevo conjunto V2 que puede usarse para generar más elementos que pertenecen a todo modelo. 42 3 Cláusulas y Programas Definitivos El proceso se repite mientras puedan generarse nuevos elementos. Los elementos agregados a Vi+1 son aquellos que se siguen inmediatamente de Vi . La construcción ası́ obtenida puede formalizarse como la iteración de una transformación T∆ sobre las interpretaciones de Herbrand de un programa ∆ . La operación se llama operador de consecuencia inmediata y se define como sigue: Definición 25 (Operador de consecuencia inmediata) Sea ground(∆ ) el conjunto de todas las cláusulas con valores asignados a todas sus variables en ∆ . T∆ es una función sobre las interpretaciones de Herbrand de ∆ definida como sigue: T∆ (V ) = {α0 | α0 ← α1 , . . . , αn ∈ ground(∆ ) ∧ {α1 , . . . , αn } ⊆ V } Para los programas definitivos, se puede mostrar que existe una interpretación mı́nima V tal que T∆ (V ) = V y que V es identica al modelo mı́nimo de Herbrand de ∆ . Más aún, el modelo mı́nimo de Herbrand es el limite de la creciente, posiblemente infinita, secuencia de iteraciones: 0, / T∆ (0), / T∆ (T∆ (0)), / ... Existe una notación estándar para denotar a los miembros de esta secuencia de interpretaciones construı́das a partir de ∆ : T∆ ↑ 0 = 0/ T∆ ↑ (i + 1) = T∆ (T∆ ↑ i) T∆ ↑ n = ∞ [ T∆ ↑ i i=0 Ejemplo 7 Tomando ∆ como el programa de impar (ej. 2, tenemos: T∆ ↑ 0 = 0/ T∆ ↑ 1 = {impar(s(0))} T∆ ↑ 2 = {impar(s(0)), impar(s(s(s(0))))} .. . T∆ ↑ m = {impar(sn (0)) | n ∈ {1, 3, 5, . . . }} Como mencionamos, el conjunto construı́do de esta manera es identico al modelo mı́nimo de Herbrand de ∆ . Teorema 3 Sea ∆ un programa definitivo y V∆ su modelo mı́nimo de Herbrand. Entonces: V∆ es la interpretación mı́nima de Herbrand tal que T∆ (V∆ ) = V∆ . V∆ = T∆ ↑ n. Capı́tulo 4 Principio de Resolución Resumen Este capı́tulo introduce el mecanismo de inferencia utilizado por la mayorı́a de los sistemas de programación lógica. Si seguimos considerando Prolog desde la perspectiva de los sistemas formales, hemos descrito ya su lenguaje y su teorı́a de modelo; ahora describiremos su teorı́a de prueba. El mecanismo en cuestión es un caso particular de la regla de inferencia llamada principio de resolución [15]. La idea es acotar el uso de este principio a programas definitivos, dando lugar a la resolución-SLD [7]. Este principio constituye el fundamento de la semántica operacional de los programas definitivos. La resolución-SLD se demostrará correcta con respecto a la teorı́a del modelo descrita en la clase anterior. 4.1. Introducción La programación lógica concierne el uso de la lógica (restringida a cláusulas) para representar y resolver problemas. Este uso es ampliamente aceptado en Inteligencia Artificial (IA), donde la idea se resume como sigue: Un problema o sujeto de investigación puede describirse mediante un conjunto de fórmulas bien formadas (fbf), de preferencia en forma de cláusulas. Si tal descripción es lo suficientemente precisa, la solución al problema o la respuesta a la pregunta planteada en la investigación, es una consecuencia lógica del conjunto de fbf que describen el problema. Por lo tanto, encontrar que fbf φ son consecuencia lógica de un conjunto de fbf ∆ , es crucial para muchas áreas de la IA, incluyendo la programación lógica. De forma que nos gustarı́a tener un procedimiento, algorı́tmico, que nos permita establecer si ∆ |= φ es el caso, o no. Este es el tema del presente capı́tulo: un método decidible conocido como principio de resolución [15]. En el caso de la lógica proposicional, la implicación lógica es decidible, es decir, existe un algoritmo que puede resolver el problema (contestar si ó no para cada caso particular ∆ |= φ ). Si n es el número de átomos distintos que ocurren en estas fbf, el número de interpretaciones posibles es finito, de hecho es 2n . Un algoritmo para 43 44 4 Principio de Resolución computar ∆ |= φ simplemente busca si φ es verdadero en todos los modelos de ∆ . ¿Qué sucede en el contexto de la lógica de primer orden? La intuición nos dice que el procedimiento de decisión de la lógica proposicional no es adecuado en primer orden, pues en este caso podemos tener una cantidad infinita de dominios e interpretaciones diferentes. Lo que es peor, el teorema de Church [2, 19], muestra que la lógica de primer orden es indecidible: Teorema 4 (Church) El problema de si ∆ |= φ , cuando ∆ es un conjunto finito arbitrario de fbf, y φ es una fbf arbitraria, es indecidible. Observen que el problema es indecidible para conjuntos arbitrarios de fbf y para una fbf φ arbitraria. No existe un algoritmo que en un número finito de pasos, de la respuesta correcta a la pregunta ¿Es φ una consecuencia lógica de ∆ ? Existen, sin embargo, procedimientos conocidos como procedimientos de prueba que pueden ser de gran ayuda para computar este problema. La idea es que cuando es el caso que ∆ |= φ , existen procedimientos que pueden verificarlo en un número finito de pasos. Por ello suele decirse que la lógica de primer orden es semi-decidible. Aunque parecerı́a trivial, siendo que ∆ |= φ , preguntar ¿∆ |= φ ?, en realidad tal trivialidad es aparente. Podemos hacer la pregunta al procedimiento sin que nosotros sepamos que ese es el caso, y obtendremos una respuesta en un número finito de pasos. Pero si es el caso que ∆ 6|= φ obtendremos la respuesta “no” (en el mejor de los casos) o el procedimiento no terminará nunca. Esto es infortunado y, peor aún, inevitable. Esta sesión introduce el procedimiento de prueba utilizado ampliamente en la programación lógica: el principio de resolución propuesto por J.A. Robinson [15]. Si bien este procedimiento está orientado a un lenguaje más expresivo que los programas lógicos definitivos, nosotros nos concentraremos en una versión del principio que aplica a programas definidos y se conoce como resolución-SLD [7] (resolución lineal con función de selección para cláusulas definitivas). 4.2. ¿Qué es un procedimiento de prueba? Hasta este momento, hemos abordado informalmente el concepto de procedimiento de prueba como la manera de generar la prueba de que una fbf φ es consecuencia lógica de un conjunto de fbf ∆ . Las fbf en ∆ se conocen como premisas y φ es la conclusión de la prueba. La prueba suele consistir de un pequeño número de transformaciones en los cuales nuevas fbf son derivadas de las premisas y de fbf previamente derivadas. Derivar una fbf implica construirla a partir de las premisas y otras fbf derivadas, siguiendo alguna regla de inferencia. Toda regla de inferencia formaliza alguna forma natural de razonamiento. Por ejemplo, el modus ponens es usado comúnmente en matemáticas, su expresión es: 4.3 Pruebas y programas lógicos 45 φ, φ → ψ ψ donde la lı́nea superior expresa las premisas y la lı́nea inferior la conclusión. Es posible ligar varias aplicaciones del modus ponens para construir una prueba. Por ejemplo, si tenemos el programa lógico ∆ = {p(a), q(b) ← p(a), r(b) ← q(b)} es posible derivar la fbf r(b) como sigue: 1. Derivar q(b) a partir de p(a) y q(b) ← p(a). 2. Derivar r(b) a partir de q(b) y r(b) ← q(b). La secuencia anterior es una prueba de que r(b) puede ser derivada de ∆ . Es evidente que si usamos modus ponens, la conclusión ψ es una consecuencia lógica de las premisas: {φ , φ → ψ} |= ψ. A esta propiedad del modus ponens se le conoce como consistencia (soundness). En general un procedimiento de prueba es consistente si todas las fbf ψ que pueden ser derivadas de algún conjunto de fbfs ∆ usando el procedimiento, son consecuencias lógicas de ∆ . En otras palabras, un procedimiento de prueba es consistente si y sólo si sólo permite derivar consecuencias lógicas de las premisas. Una segunda propiedad deseable de los procedimientos de prueba es su completez. Un procedimiento de prueba es completo si toda fbf que es una consecuencia lógica de las premisas ∆ , puede ser derivada usando el procedimiento en cuestión. El modus ponens por si mismo, no es completo. Por ejemplo, no existe secuencia alguna de aplicaciones del modus ponens que deriven la fbf p(a) de ∆ = {p(a) ∧ p(b)}, cuando es evidente que ∆ |= p(a). La regla ψφ es completa, pero no válida. !Nos permite extraer cualquier conclusión, a partir de cualquier premisa! Esto ejemplifica que obtener completitud es sencillo, pero obtener completitud y correctez, no lo es. 4.3. Pruebas y programas lógicos Recordemos que los enunciados en los programas lógicos tienen la estructura general de la implicación lógica: α0 ← α1 , . . . , αn (n ≥ 0) donde α0 , . . . , αn son fbfs atómicas y α0 puede estar ausente (para representar cláusulas meta). Consideren el siguiente programa definitivo ∆ que describe un mundo donde los padres de un recién nacido están orgullosos, Juan es el padre de Marta y Marta es una recién nacida: 46 4 Principio de Resolución orgulloso(X) ← padre(X,Y ), recien nacido(Y ). padre(X,Y ) ← papa(X,Y ). padre(X,Y ) ← mama(X,Y ). papa( juan, marta). recien nacido(marta). Observen que el programa describe únicamente conocimiento positivo, es decir, no especifica quién no está orgulloso. Tampoco que significa para alguien no ser padre. Supongamos que deseamos contestar la pregunta ¿Quién está orgulloso? Esta pregunta concierne al mundo descrito por nuestro programa, esto es, concierne al modelo previsto para ∆ . La respuesta que esperamos es, por supuesto, juan. Ahora, recuerden que la lógica de primer orden no nos permite expresar enunciados interrogativos, por lo que nuestra pregunta debe formalizarse como una cláusula meta (enunciado declarativo): ← orgulloso(Z). que es una abreviatura de ∀Z¬orgulloso(Z) (una cláusula definitiva sin cabeza), que a su vez es equivalente de: ¬∃Z orgulloso(Z). cuya lectura es “Nadie está orgulloso”, esto es, la respuesta negativa a la consulta original – ¿Quién está orgulloso? La meta ahora es probar que este enunciado es falso en todo modelo del programa ∆ y en particular, es falso en el modelo previsto para ∆ , puesto que esto es una forma de probar que ∆ |= ∃Z orgulloso(Z). En general para todo conjunto de fbf cerradas ∆ y una fbf cerrada γ, tenemos que ∆ |= γ si ∆ ∪ {¬γ} es no satisfacerle (no tiene modelo). Por lo tanto, nuestro objetivo es encontrar una substitución θ tal que el conjunto ∆ ∪ {¬orgulloso(Z)θ } sea no satisfacerle, o de manera equivalente, ∆ |= ∃Z orgulloso(Z)θ . El punto inicial de nuestro razonamiento es asumir la meta G0 – Para cualquier Z, Z no está orgulloso. La inspección del programa ∆ revela que una regla describe una condición para que alguien esté orgulloso: orgulloso(X) ← padre(X,Y ), recien nacido(Y ). lo cual es lógicamente equivalente a: ∀(¬orgulloso(X) ⇒ ¬(padre(X,Y ) ∧ recien nacido(Y ))) Al renombrar X por Z, eliminar el cuantificador universal y usar modus ponens con respecto a G0 , obtenemos: 4.3 Pruebas y programas lógicos 47 ¬(padre(Z,Y ) ∧ recien nacido(Y )) o su equivalente: ← padre(Z,Y ), recien nacido(Y ). al que identificaremos como G1 . Un paso en nuestro razonamiento resulta en remplazar la meta G0 por la meta G1 que es verdadera en todo modelo ∆ ∪ {G0 }. Ahora solo queda probar que ∆ ∪ {G1 } es no satisfacible. Observen que G1 es equivalente a la fbf: ∀Z∀Y (¬padre(Z,Y ) ∨ ¬recien nacido(Y )) Por lo tanto, puede probarse que la meta G1 es no satisfacible para ∆ , si en todo modelo de ∆ hay una persona que es padre de un recién nacido. Entonces, verificamos primero si hay padres con estas condiciones. El programa contiene la cláusula: padre(X,Y ) ← papa(X,Y ). que es equivalente a: ∀(¬padre(X,Y ) ⇒ ¬papa(X,Y )) por lo que G1 se reduce a: ← papa(Z,Y ), recien nacido(Y ). que identificaremos como G2 . Se puede mostrar que no es posible satisfacer la nueva meta G2 con el programa ∆ , si en todo modelo de ∆ hay una persona que es papá de un recién nacido. El programa declara que juan es padre de marta: papa( juan, marta). ası́ que sólo resta probar que “marta no es una recién nacida” no se puede satisfacer junto con ∆ : ← recien nacido(marta). pero el programa contiene el hecho: recien nacido(marta). equivalente a ¬recien nacido(marta) ⇒ f lo que conduce a una refutación. Este razonamiento puede resumirse de la siguiente manera: para probar la existencia de algo, suponer lo contrario y usar modus ponens y la regla de eliminación del cuantificador universal, para encontrar un contra ejemplo al supuesto. Observen que la meta definitiva fue convertida en un conjunto de átomos a ser probados. Para ello, se seleccionó una fbf atómica de la meta p(s1 , . . . , sn ) y una 48 4 Principio de Resolución cláusula de la forma p(t1 , . . . ,tn ) ← A1 , . . . An para encontrar una instancia común de p(s1 , . . . , sn ) y p(t1 , . . . ,tn ), es decir, una substitución θ que hace que p(s1 , . . . , sn )θ y p(t1 , . . . ,tn )θ sean idénticos. Tal substitución se conoce como unificador. La nueva meta se construye remplazando el átomo seleccionado en la meta original, por los átomos de la cláusula seleccionada, aplicando θ a todos los átomos obtenidos de esta manera. El paso de computación básico de nuestro ejemplo, puede verse como una regla de inferencia puesto que transforma fórmulas lógicas. Lo llamaremos principio de resolución SLD para programas definitivos. Como mencionamos, el procedimiento combina modus ponens, eliminación del cuantificador universal y en el paso final un reductio ad absurdum. Cada paso de razonamiento produce una substitución, si se prueba en k pasos que la meta definida en cuestión no puede satisfacerse, probamos que: ← (A1 , . . . Am )θ1 . . . θk es una instancia que no puede satisfacerse. De manera equivalente, que: ∆ |= (A1 ∧ · · · ∧ Am )θ1 . . . θk Observen que generalmente, la computación de estos pasos de razonamiento no es determinista: cualquier átomo de la meta puede ser seleccionado y pueden haber varias cláusulas del programa que unifiquen con el átomo seleccionado. Otra fuente de indeterminismo es la existencia de unificadores alternativos para dos átomos. Esto sugiere que es posible construir muchas soluciones (algunas veces, una cantidad infinita de ellas). Por otra parte, es posible también que el atomo seleccionado no unifique con ninguna cláusula en el programa. Esto indica que no es posible construir un contra ejemplo para la meta definida inicial. Finalmente, la computación puede caer en un ciclo y de esta manera no producir solución alguna. 4.4. Substitución Una substitución remplaza variables por términos, por ejemplo, podemos remplazar la variable X por el término f (a) en la cláusula p(X) ∨ q(X), y ası́ obtener la nueva cláusula p( f (a))∨q( f (a)). Si asumimos que las cláusulas están cuantificadas universalmente, decimos que está substitución hace a la cláusula original, “menos general”. Mientras que la cláusula original dice que V (p(X)) = t y que V (q(X)) = t para cualquier X en el dominio, la segunda cláusula dice que esto sólo es cierto cuando cuando V (X) = f (a). Observen que la segunda cláusula es consecuencia lógia de la primera: p(X) ∨ q(X) |= p( f (a)) ∨ q( f (a)) Definición 26 (Substitución) Una substitución θ es un conjunto finito de la forma: {X1 /t1 , . . . , Xn /tn }, (n ≥ 0) 4.4 Substitución 49 donde las Xi son variables, distintas entre si, y los ti son términos. Decimos que ti substituye a Xi . La forma Xi /ti se conoce como ligadura de Xi . La substitución θ se dice se dice de base (grounded) si cada término ti es un término base (no incluye variables).. La substitución dada por el conjunto vacı́o, se conoce como substitución de identidad o substitución vacı́a y se denota por ε. La restricción de θ sobre un conjunto de variables Var es la substitucion {X/t ∈ θ | X ∈ Var}. Ejemplo 8 {Y /X, X/g(X,Y )} y {X/a,Y / f (Z), Z/( f (a), X1 /b} son substituciones. La restricción de la segunda substitución sobre {X, Z} es {X/a, Z/ f (a)}. Definición 27 (Expresión) Una expresión es un término, una literal, o una conjunción o disyunción de literales. Una expresión simple es un término o una literal. Observen que una cláusula es una expresión. Las substituciones pueden aplicarse a las expresiones, lo que significa que las variables en las expresiones serán remplazadas de acuerdo a la substitución. Definición 28 Sea θ = {X1 /t1 , . . . , Xn /tn } una substitución y α una expresión. Entonces αθ , la ocurrencia (instance) de α por θ , es la expresión obtenida al substituir simultáneamente Xi por ti para 1 ≤ i ≤ n. Si αθ es una expresión de base, se dice que es una ocurrencia base y se dice que θ es una substitución de base para α. Si Σ = {α1 , . . . , αn } es un conjunto finito de expresiones, entonces Σ θ denota {α1 θ , . . . , αn θ }. Ejemplo 9 Sea α la expresión p(Y, f (X)) y sea θ la substitución {X/a,Y /g(g(X))}. La ocurrencia de α por θ es αθ = p(g(g(X)), f (a). Observen que X e Y son simultáneamente remplazados por sus respectivos términos, lo que implica que X en g(g(X)) no es afectada por X/a. Si α es una expresión cerrada que no es un término, por ejemplo, una literal, o una conjunción o disyunción de literales, y θ es una substitución, lo siguiente se cumple: α |= αθ por ejemplo: p(X) ∨ ¬q(Y ) |= p(a) ∨ ¬q(Y ) donde hemos usado la substitución {X/a}. Podemos aplicar una substitución θ y luego aplicar una substitución σ , a lo cual se llama composición de las substituciones θ y σ . Si ese es el caso, primero se aplica θ y luego σ . Las composiciones pueden verse como mapeos del conjunto de variables en el lenguaje, al conjunto de términos. Definición 29 (Composición) Sean θ = {X1 /s1 , . . . , Xm /sm } y σ = {Y1 /t1 , . . .Yn /tn } dos substituciones. Consideren la secuencia: X1 /(s1 σ ), . . . , Xm /(sm σ ),Y1 /t1 , . . . ,Yn /tn 50 4 Principio de Resolución Si se borran de esta sencuencia las ligaduras Xi /si σ cuando Xi = si σ y cualquier ligadura Y j /t j donde Y j ∈ {X1 , . . . , Xm }. La substitución consistente en las ligaduras de la secuencia resultante es llamada composición de θ y σ , se denota por θ σ . Ejemplo 10 Sea θ = {X/ f (Y ), Z/U} y σ = {Y /b,U/Z}. Construimos la secuencia de ligaduras X/( f (Y )σ ), Z/(u)σ ,Y /b,U/Z lo cual es X/ f (b), Z/Z,Y /b,U/Z. Al borrar la ligadura Z/Z obtenemos la secuencia X/ f (b),Y /b,U/Z = θ σ . Definición 30 (Ocurrencia) Sean θ y σ dos substituciones. Se dice que θ es una ocurrencia de σ , si existe una substitución γ, tal que σ γ = θ . Ejemplo 11 La substitución θ = {X/ f (b),Y /a} es una ocurrencia de la substitución σ = {X/ f (X),Y /a}, puesto que σ {X/b} = θ . Algunas propiedades sobre las substituciones incluyen: Proposición 3 Sea α una expresión, y sea θ , σ y γ substituciones. Las siguientes relaciones se cumplen: 1. θ = θ ε = εθ 2. (αθ )σ = α(θ σ ) 3. θ σ )γ = θ (σ γ) 4.5. Unificación Uno de los pasos principales en el ejemplo de la sección 4.3, consistió en hacer que dos fbf atómicas se vuelvan sintácticamente equivalentes. Este proceso se conoce como unificación y posee una solución algorı́tmica. Definición 31 (Unificador) Sean α y β términos. Una substitución θ tal que α y β sean idénticos (αθ = β θ ) es llamada unificador de α y β . Ejemplo 12 uni f ica(conoce( juan, X), conoce( juan, maria)) = {X/maria} uni f ica(conoce( juan, X), conoce(Y, Z)) = {Y / juan, X/Z} = {Y / juan, X/Z,W /pedro} = {Y / juan, X/ juan, Z/ juan} Definición 32 (Generalidad entre substituciones) Una substitución θ se dice más general que una substitución σ , si y sólo si existe una substitución γ tal que σ = θ γ. Definición 33 (MGU) Un unificador θ se dice el unificador más general (MGU) de dos términos, si y sólo si θ es más general que cualquier otro unificador entre esos términos. 4.5 Unificación 51 Definición 34 (Forma resuelta) Un conjunto de ecuaciones {X1 = t1 , . . . , Xn = tn } está en forma resuelta, si y sólo si X1 , . . . , Xn son variables distintas que no ocurren en t1 , . . . ,tn . Existe una relación cercana entre un conjunto de ecuaciones en forma resuelta y el unificador más general de ese conjunto: Sea {X1 = t1 , . . . , Xn = tn } un conjunto de ecuaciones en forma resuelta. Entonces {X1 /t1 , . . . , Xn /tn } es un MGU idempotente de la forma resuelta. Definición 35 (Equivalencia en conjuntos de ecuaciones) Dos conjuntos de ecuaciones E1 y E2 se dicen equivalentes, si tienen el mismo conjunto de unificadores. La definición puede usarse como sigue: para computar el MGU de dos términos α y β , primero intente transformar la ecuación {α = β } en una forma resuelta equivalente. Si esto falla, entonces mgu(α, β ) = f allo. Sin embargo, si una forma resuelta {X1 = t1 , . . . , Xn = tn } existe, entonces mgu(α, β ) = {X1 /t1 , . . . , Xn /tn }. Un algoritmo para encontrar la forma resuelta de un conjunto de ecuaciones es como sigue: Algoritmo 1 Unifica(E) 1: function U NIFICA(E) . E es un conjunto de ecuaciones 2: repeat 3: (s = t) ← seleccionar(E) 4: if f (s1 , . . . , sn ) = f (t1 , . . . ,tn ) (n ≥ 0) then 5: remplazar (s = t) por s1 = t1 , . . . , sn = tn 6: else if f (s1 , . . . , sm ) = g(t1 , . . . ,tn ) ( f /m 6= g/n) then 7: return(fallo) 8: else if X = X then 9: remover la X = X 10: else if t = X then 11: remplazar t = X por X = t 12: else if X = t then 13: if subtermino(X,t) then 14: return(fallo) 15: else remplazar todo X por t 16: end if 17: end if 18: until No hay accion posible para E 19: end function Ejemplo 13 El conjunto { f (X, g(Y )) = f (g(Z), Z)} tiene una forma resuelta, puesto que: ⇒ {X = g(Z), g(Y ) = Z} ⇒ {X = g(Z), Z = g(Y )} ⇒ {X = g(g(Y )), Z = g(Y )} 52 4 Principio de Resolución Ejemplo 14 El conjunto { f (X, g(X), b) = f (a, g(Z), Z)} no tiene forma resuelta, puesto que: ⇒ {X = a, g(X) = g(Z), b = Z} ⇒ {X = a, g(a) = g(Z), b = Z} ⇒ {X = a, a = Z, b = Z} ⇒ {X = a, Z = a, b = Z} ⇒ {X = a, Z = a, b = a} ⇒ f allo Ejemplo 15 El conjunto { f (X, g(X)) = f (Z, Z)} no tiene forma resuelta, puesto que: ⇒ {X = Z, g(X) = Z} ⇒ {X = Z, g(Z) = Z} ⇒ {X = Z, Z = g(Z)} ⇒ f allo Este algoritmo termina y regresa una forma resuelta equivalente al conjunto de ecuaciones de su entrada; o bien regresa fallo si la forma resuelta no existe. Sin embargo, el computar subtermino(X,t) (verificación de ocurrencia) hace que el algoritmo sea altamente ineficiente. Los sistemas Prolog resuelven este problema haciéndo caso omiso de la verificación de ocurrencia. El standard ISO Prolog (1995) declara que el resultado de la unificación es no decidible. Al eliminar la verificación de ocurrencia es posible que al intentar resolver X = f (X) obtengamos X = f ( f (X)) · · · = f ( f ( f . . . )). En la practica los sistemas Prolog no caen en este ciclo, pero realizan la siguiente substitución {X/ f (∞)}. Si bien esto parece resolver el problema de eficiencia, generaliza el concepto de término, substitución y unificación al caso del infinito, no considerado en la lógica de primer orden, introduciéndo a su vez inconsistencia. 4.6. Resolución-SLD El método de razonamiento descrito informalmente al inicio de esta sesión, puede resumirse con la siguiente regla de inferencia: ∀¬(α1 ∧ · · · ∧ αi−1 ∧ αi ∧ αi+1 ∧ · · · ∧ αm ) ∀(β0 ← β1 ∧ · · · ∧ βn ) ∀¬(α1 ∧ · · · ∧ αi−1 ∧ β1 ∧ · · · ∧ βn ∧ αi+1 ∧ · · · ∧ αm )θ o, de manera equivalente, usando la notación de los programas definitivos: 4.6 Resolución-SLD 53 ← α1 , . . . , αi−1 , αi , αi+1 , . . . , αm β0 ← β1 , . . . , βn ← (α1 , . . . , αi−1 , β1 , . . . , βn , . . . , αm )θ donde: 1. α1 , . . . , αm son fbf atómicas. 2. β0 ← β1 , . . . , βn es una cláusula definitiva en el programa ∆ (n ≥ 0). 3. MGU(αi , β0 ) = θ . La regla tiene dos premisas: una meta y una cláusula definitivas. Observen que cada una de ellas está cuantificada universalmente, por lo que el alcance de los cuantificadores es disjunto. Por otra parte, solo hay un cuantificador universal para la conclusión, por lo que se requiere que el conjunto de variables en las premisas sea disjunto. Puesto que todas las variables en las premisas están cuantificadas, es siempre posible renombrar las variables de la cláusula definitiva para cumplir con esta condición. La meta definida puede incluir muchas fbf atómicas que unifican con la cabeza de alguna cláusula en el programa. En este caso, es deseable contar con un mecanismo determinista para seleccionar un átomo αi a unificar. Se asume una función que selecciona una submeta de la meta definida (función de selección). La regla de inferencia presentada es la única necesaria para procesar programas definitivos. Esta regla es una versión de la regla de inferencia conocida como principio de resolución, introducido por J.A. Robinson en 1965. El principio de resolución aplica a cláusulas. Puesto que las cláusulas definitivas son más restringidas que las cláusulas, la forma de resolución presentada se conoce como resolución-SLD (resolución lineal para cláusulas definitivas con función de selección). El punto de partida de la aplicación de esta regla de inferencia es una meta definida G0 : ← α1 , . . . , αm (m ≥ 0) De esta meta, una submeta αi será seleccionada, de preferencia por una función de selección. Una nueva meta G1 se construye al seleccionar una cláusula del programa β0 ← β1 , . . . , βn (n ≥ 0) cuya cabeza β0 unifica con αi , resultando en θ1 . G1 tiene la forma: ← (α1 , . . . , αi−1 , β1 , . . . , βn , . . . , αm )θ1 Ahora es posible aplicar el principio de resolución a G1 para obtener G2 , y ası́ sucesivamente. El proceso puede terminar o no. Hay dos situaciones donde no es posible obtener Gi+1 a partir de Gi : 1. cuando la submeta seleccionada no puede ser resuelta (no es unificable con la cabeza de una cláusula del programa). 2. cuando Gi = 2 (meta vacı́a = f). Definición 36 (Derivación-SLD) Sea G0 una meta definitiva, ∆ un programa definitivo y R una función de selección. Una derivación SLD de G0 (usando ∆ y R) es una secuencia finita o infinita de metas: 54 4 Principio de Resolución G0 α0 G1 . . . Gn−1 αn−1 Gn Para manejar de manera consistente el renombrado de variables, las variables en una cláusula αi serán renombradas poniéndoles subı́ndice i. Cada derivación SLD nos lleva a una secuencias de MGUs θ1 , . . . , θn . La composición ( θ1 θ2 . . . θn si n > 0 θ= ε si n = 0 de MGUs se conoce como la substitución computada de la derivación. Ejemplo 16 Consideren la meta definida ← orgulloso(Z) y el programa discutido en la clase anterior. G0 =← orgulloso(Z). α0 = orgulloso(X0 ) ← padre(X0 ,Y0 ), recien nacido(Y0 ). La unificación de orgulloso(Z) y orgulloso(X0 ) nos da el MGU θ1 = {X0 /Z}. Asumamos que nuestra función de selección es tomar la submeta más a la izquierda. El primer paso de la derivación nos conduce a: G1 =← padre(Z,Y0 ), recien nacido(Y0 ). α1 = padre(X1 ,Y1 ) ← papa(X1 ,Y1 ). En el segundo paso de la resolución el MGU θ2 = {X1 /Z,Y1 /Y0 } es obtenido. La derivación continua como sigue: G2 =← papa(Z,Y0 ), recien nacido(Y0 ). α2 = papa( juan, marta). G3 =← recien nacido(marta). α3 = recien nacido(marta). G4 = 2 la substitución computada para esta derivación es: θ1 θ2 θ3 θ4 = {X0 /Z}{X1 /Z,Y1 /Y0 }{Z/ juan,Y0 /marta}ε = {X0 / juan, X1 / juan,Y1 /marta, Z/ juan,Y0 /marta} Las derivaciones SLD que terminan en la meta vacı́a (2) son de especial importancia pues corresponden a refutaciones a la meta inicial (y proveen las respuestas a la meta). Definición 37 (Refutación SLD) Una derivación SLD finita: 4.6 Resolución-SLD 55 G0 α0 G1 . . . Gn−1 αn−1 Gn donde Gn = 2, se llama refutación SLD de G0 . Definición 38 (Derivación fallida) Una derivación de la meta definitiva G0 cuyo último elemento no es la meta vacı́a y no puede resolverse con ninguna cláusula del programa, es llamada derivación fallida. Definición 39 (Arbol-SLD) Sea ∆ un programa definitivo, G0 una meta definitiva, y R una función de selección. El árbol-SLD de G0 (usando ∆ y R) es un árbol etiquetado, posiblemente infinito, que cumple las siguientes condiciones: La raı́z del árbol está etiquetada por G0 . Si el árbol contiene un nodo etiquetado como Gi y existe una cláusula renombrada αi ∈ ∆ tal que Gi+1 es dervidada de Gi y αi via R, entonces el nodo etiquetado como Gi tiene un hijo etiquetado Gi+1 El arco que conecta ambos nodos está etiquetado como αi . Por ejemplo: ← orgulloso(Z) ← padre(Z,Y0 ), recien nacido(Y0 ) ← papa(Z,Y0 ), recien nacido(Y0 ) ← mama(Z,Y0 ), recien nacido(Y0 ) ← recien nacido(marta) 2 4.6.1. Propiedades de la resolución-SLD Definición 40 (Consistencia) Sea ∆ un programa definitivo, R una función de selección, y θ una substitución de respuesta computada a partir de ∆ y R para una meta ← α1 , . . . , αm . Entonces ∀((α1 ∧ · · · ∧ αm )θ ) es una consecuencia lógica del programa ∆ . Definición 41 (Compleción) Sea ∆ un programa definitivo, R una función de selección y ← α1 , . . . , αm una meta definitiva. Si ∆ |= ∀((α1 ∧ · · · ∧ αm )σ ), entonces existe una refutación de ← α1 , . . . , αm vı́a R con una substitución de respuesta computada θ , tal que (α1 ∧ · · · ∧ αm )σ es un caso de (α1 ∧ · · · ∧ αm )θ . Capı́tulo 5 Negación Resumen Si los programas definitivos representan únicamente conocimiento positivo sobre un problema ¿Cómo es que se pueden computar consecuencias lógicas negativas? Este capı́tulo introduce los conceptos de supuesto del mundo cerrado (CWA) y su forma más relajada, conocida como negación por fallo finito (NAF), para introducir el manejo de conocimiento negativo en los programas definitivos. Se introducen también los conceptos de compleción de programa y resolución-SLDNF. Finalmente abordaremos el concepto de programas generales y la resolución para este tipo de programas. 5.1. Introducción Los programas definitivos expresan conocimiento positivo, en el sentido que los hechos y las reglas describen que ciertos objetos están en cierta relación con otros. Las relaciones se hacen explı́citas en el modelo mı́nimo de Herbrand – el conjunto de todas las consecuencias atómicas de base de un programa. Por ejemplo, consideren el siguiente programa ∆ : sobre(X,Y ) ← en(X,Y ). sobre(X,Y ) ← en(X, Z), sobre(Z,Y ). en(c, b). en(b, a). El modelo mı́nimo de Herbrand para este programa es el siguiente: {en(b, a), en(c, b), sobre(b, a), sobre(c, b), sobre(c, a)} Observen que ni el programa, ni el modelo mı́nimo de Herbrand, incluyen información negativa del tipo: a no está sobre b, o b no está sobre c. Sin embargo, 57 58 5 Negación nosotros usamos información negativa implı́cita en algunos casos. Por ejemplo, si cuando buscamos un boleto de autobús, nos encontramos con que no aparecen salidas a México a las 10:12 am, asumimos que tal salida no existe. La ausencia de información se asume como evidencia de lo contrario. Esto es posible porque, como en el caso de autobús, asumimos que toda la información disponible está a nuestro alcance. La idea anterior se puede formular con la suposición del mundo cerrado (Closed-World Assumption) ó CWA, una pseudo-regla de inferencia que expresa: ∆ 6` α (CWA) ¬α Si una fbf atómica de base (sin variables) α, no puede derivarse del programa ∆ siguiendo las reglas de inferencia del sistema, entonces puede derivarse ¬α. En el caso de los sistemas correctos y completos, la condición ∆ 6` α es equivalente a ∆ 6|= α. Como este es el caso para la resolución-SLD, la condición puede ser remplazada por α 6∈ M∆ . Por ejemplo, la fbf sobre(b, c) no puede ser derivada por resolución-SLD a partir del programa ∆ (vean el árbol de derivación en la figura 5.1). En realidad sobre(b, c) no puede ser derivada por ningún sistema correcto, puesto que no es una consecuencia lógica de ∆ . Dada la completitud de la resolución-SLD, se sigue que ∆ 6|= sobre(b, c) y usando la CWA inferimos que ¬sobre(b, c). ← sobre(b, c) ← en(b, c) ← en(b, Z0 ), sobre(Z0 , c) ← sobre(a, c) ← en(a, c) ← en(a, Z2 ), sobre(Z2 , c) Figura 5.1 Árbol de derivación-SLD fallido En contra de lo que podrı́a ser nuestra primera intuición, existen problemas asociados a la CWA. El principal tiene que ver con que la no-derivabilidad para los programas definitivos es no decidible en el caso general. Esto es, no es posible determinar si la pseudo-regla asociada al CWA aplica o no. Una versión más débil de la suposición de mundo cerrado, se logra si asumimos que ¬α es derivable a partir del programa ∆ si la meta ← α tiene un árbol-SLD finito que falla. A esta regla se le conoce como negación como falla (finita) (NAF). Es necesario contrastar la NAF con la CWA, que también puede verse como una negación por falla, pero infinita. Para ilustrar la diferencia entre los dos enfo- 5.2 La compleción de un programa 59 ques extendamos el programa ∆ con la siguiente cláusula evidentemente verdadera sobre(X,Y ) ← sobre(X,Y ). El árbol-SLD de la meta ← sobre(b, c) sigue sin contener refutaciones, pero ahora es infinito. Por lo tanto no podemos concluir que ¬sobre(b, c) usando NAF, pero si usando CWA. Pero el problema más serio con estos enfoques es que son incorrectos, ¬sobre(b, c) no es una consecuencia lógica del programa ∆ . En lo general, cualquier sistema que permita inferir literales negativas a partir de un programa definitivo, es incorrecto. La razón es que la base de Herbrand del programa B∆ , en el cual todas las fbf atómicas cerradas son verdaderas, es siempre un modelo de ∆ . Existen dos aproximaciones a la solución de estos problemas: ver los programas como resúmenes de programas más extensos que validan las literales negativas; o redefinir la noción de consecuencia lógica de forma que sólo algunos modelos del programa (el mı́nimo de Herbrand, por ejemplo) sean tomados en cuenta. En ambos casos, el efecto es descartar algunos modelos del programa que no son interesantes. Primero justificaremos la regla NAF en términos de la compleción de los programas definitivos y posteriormente, extenderemos el lenguaje de los programas definitivos para incluir en ellos literales negativas en la cabeza y cuerpo de las cláusulas. 5.2. La compleción de un programa La idea que presentaremos a continuación se debe a K. Clark [3] y se basa en que cuando uno escribe un programa definitivo ∆ , en realidad quiere expresar algo más que su conjunto de cláusulas definitivas. El programa deseado puede formalizarse como la compleción de ∆ . Consideren la siguiente definición: sobre(X,Y ) ← en(X,Y ). sobre(X,Y ) ← en(X, Z), sobre(Z,Y ). Estas reglas especifican que un objeto está sobre un segundo objeto, si el primer objeto está encima del segundo (1) ó si el objeto está sobre otro objeto que a su vez está encima del segundo (2). Esto también puede escribirse como: sobre(X,Y ) ← en(X,Y ) ∨ (en(X, Z), sobre(Z,Y )) Ahora, ¿Qué sucede si remplazamos la implicación por la equivalencia lógica? sobre(X,Y ) ↔ en(X,Y ) ∨ (en(X, Z), sobre(Z,Y )) Está fbf expresa que X está sobre Y si y sólo si una de las condiciones es verdadera. Esto es, si ninguna de las condiciones se cumple, ¡se sigue que X no está sobre Y ! Esta es la intuición seguida para explicar la negación como falla. 60 5 Negación Desafortunadamente, combinar cláusulas definitivas como en el ejemplo anterior, sólo es posible para cláusulas con cabezas idénticas. Por ejemplo: en(c, b). en(b, a). Por una simple transformación, el programa puede ser escrito como: en(X1 , X2 ) ← X1 = c, X2 = b en(X1 , X2 ) ← X1 = b, X2 = a Las cláusulas pueden combinarse en una sola fórmula, donde la implicación es remplazada por la equivalencia lógica. en(X1 , X2 ) ↔ (X1 = c, X2 = b) ∨ (X1 = b, X2 = a) La lectura lógica de esta fbf es que X1 está en X2 si y sólo si X1 = c y X2 = b o si X1 = b y X2 = a. Esta transformación se puede realizar sobre un programa lógico definitivo ∆ y el resultado se conoce como compleción de ∆ . Definición 42 (Compleción) Sea ∆ un programa lógico definitivo. La compleción comp(∆ ) de ∆ es el conjunto de fórmulas obtenido a partir de las siguientes tres transformaciones: 1. Para cada sı́mbolo de predicado φ remplazar la cláusula α de la forma: φ (t1 , . . . ,tm ) ← α1 , . . . , αn (n ≥ 0) por la fórmula: φ (X1 , . . . , Xm ) ← ∃Y1 , . . . ,Yi (X1 = t1 , . . . , Xm = tm , α1 , . . . , αn ) donde las Yi son todas variables en α y las Xi son variables únicas que no aparecen en α. 2. Para cada sı́mbolo de predicado φ remplazar todas las fbf: φ (X1 , . . . , Xm ) ← β1 .. . φ (X1 , . . . , Xm ) ← β j por la fórmula: 5.2 La compleción de un programa 61 ∀X1 , . . . , Xm (φ (X1 , . . . , Xm ) ↔ β1 ∨, . . . , ∨β j si j > 0 ∀X1 , . . . , Xm (¬φ (X1 , . . . , Xm )) si j = 0 3. Finalmente el programa se extiende con los siguientes axiomas de igualdad libre, que definen las igualdades introducidas en el paso 1: ∀(X = X) ∀(X = Y ⇒ Y = X) ∀(X = Y ∧Y = Z ⇒ X = Z) ∀(X1 = Y1 ∧ · · · ∧ Xn = Yn ⇒ f (X1 , . . . , Xn ) = f (Y1 , . . . ,Yn )) ∀(X1 = Y1 ∧ · · · ∧ Xn = Yn ⇒ (φ (X1 , . . . , Xn ) ⇒ φ (Y1 , . . . ,Yn )) ∀( f (X1 , . . . , Xn ) = f (Y1 , . . . ,Yn ) ⇒ X1 = Y1 ∧ · · · ∧ Xn = Yn ) ∀(¬ f (X1 , . . . , Xm ) = g(Y1 , . . . ,Yn ))(Si f /m 6= g/n) ∀(¬X = t)(Si X es un subtermino propio de t) Estas definiciones garantizan que la igualdad (=) sea una relación de equivalencia; que sea una relación congruente; y que formalice la noción de unificación. Las primeros cinco definiciones se pueden abandonar si se especifica que = representa la relación de identidad . Ejemplo 17 Consideremos la construcción de comp(∆ ) tal y como se definió anteriormente. El primer paso produce: sobre(X1 , X2 ) ← ∃X,Y (X1 = X, X2 = Y, en(X,Y )) sobre(X1 , X2 ) ← ∃X,Y, Z (X1 = X, X2 = Y, en(Z,Y ), sobre(Z,Y )) en(X1 , X2 ) ← (X1 = c, X2 = b) en(X1 , X2 ) ← (X1 = b, X2 = a) dos pasos más adelante obtenemos: ∀X1 , X2 (sobre(X1 , X2 ↔ ∃X,Y (. . . ) ∧ ∃X,Y, Z(. . . )) ∀X1 , X2 (en(X1 , X2 ) ↔ (X1 = c, X2 = b) ∧ (X1 = b, X1 = a)) y el programa se termina con las definiciones de igualdad como identidad y unificación. La compleción comp(∆ ) de un programa definitivo ∆ preserva todas las literales positivas modeladas por ∆ . Esto es, si ∆ |= α entonces comp(∆ ) |= α. Tampoco se agrega información positiva al completar el programa: Si comp(∆ ) |= α entonces ∆ |= α. Por lo tanto, al completar el programa no agregamos información positiva al mismo, solo información negativa. 62 5 Negación Como sabemos, no es posible que una literal negativa pueda ser consecuencia lógica de un programa definitivo. Pero al substituir las implicaciones en ∆ por equivalencias en comp(∆ ) es posible inferir información negativa a partir del programa completado. Esta es la justificación de la regla NAF, cuyas propiedades de consistencia se deben a K. Clark [3]: Teorema 5 (Consistencia de la NAF) Sea ∆ un programa definitivo y ← α una meta definitiva. Si ← α tiene un árbol-SLD finito fallido, entonces comp(∆ ) |= ∀(¬α). La consistencia se preserva aún si α no es de base. Por ejemplo, ← en(a, X) falla de manera finita y por lo tanto, se sigue que comp(∆ ) |= ∀(¬en(a, X)). La completitud de la NAF también ha sido demostrada: Teorema 6 (Completitud de la NAF) Sea ∆ un programa definitivo. Si comp(∆ ) |= ∀(¬α) entonces existe un árbol finito fallido para la meta definitiva ← α. Observen que solo enuncia la existencia de un árbol-SLD finito fallido. Como se ha mencionado, un árbol-SLD puede ser finito bajo ciertas reglas de computación e infinito bajo otras. En particular, el teorema de completitud no es válido para las reglas de computación de Prolog. La completitud funciona para una subclase de derivaciones-SLD conocidas como justas (fair), las cuales o bien son finitas o garantizan que cada átomo en la derivación (u ocurrencia de éste), es seleccionado eventualmente por las reglas de computación. Un árbol-SLD es justo si todas sus derivaciones son justas. La NAF es completa para árboles-SLD justos. Este tipo de derivaciones se pueden implementar fácilmente: selecciona la sub-meta más a la izquierda y agrega nuevas submetas al final de esta (búsqueda en amplitud). Sin embargo, pocos sistemas implementan tal estrategia por razones de eficiencia. 5.3. Resolución SLDNF para programas definitivos En el capı́tulo 4 presentamos el método de resolución-SLD, utilizado para probar si una literal positiva cerrada es consencuencia lógica de un programa. En la sección anterior afirmamos que también las literales negadas pueden derivarse a partir de la terminación de programas lógicos definitivos. Combinando la resolución SLD y la negación como fallo finito (NAF), es posible generalizar la noción de meta definitiva para incluir literales positivas y negadas. Tales metas se conocen como generales. Definición 43 (Meta general) Una meta general tiene la forma: ← α1 , . . . αn (n ≥ 0) donde cada αi es una literal positiva o negada. La combinación de la resolución SLD y la NAF se llama resolución SLDNF. 5.3 Resolución SLDNF para programas definitivos 63 Definición 44 (Resolución SLDNF para programas definitivos) Sea ∆ un programa definitivo, G0 una meta general y R una función de selección (también conocida como regla de computación). Una derivación SLDNF de G0 usando R, es una secuencia finita o infinita de metas generales: G0 donde Gi αi α0 G1 . . . Gn−1 αn−1 Gn Gi+1 puede ocurrir si: 1. la literal R-seleccionada en Gi es positiva y Gi+1 se deriva de Gi y αi por un paso de resolución SLD; 2. la literal R-seleccionada en Gi es negativa (¬α) y la meta ← α tiene un árbol SLD fallido y finito y Gi+1 se obtiene a partir de Gi eliminando ¬α (en cuyo caso αi , corresponde al marcador especial FF). Cada paso en una derivación SLDNF produce una substitución, en el caso 1 un MGU y en el caso 2, la substitución vacı́a ε. Entonces, una literal negativa ¬α es demostrada si ← α tiene un árbol SLD finito que falla. Por dualidad, ¬α falla de manera finita si α es demostrada. Además de la refutación y de la derivación infinita, existen dos clases de derivaciones SLDNF completas dada una función de selección: 1. Una derivación se dice (finitamente) fallida si (i) la literal seleccionada es positiva y no unifica con ninguna cabeza de las cláusulas del programa, o (2) la literal seleccionada es negativa y tiene un fallo finito. 2. Una derivación se dice plantada (stuck) si la sub-meta seleccionada es de la forma ¬α y ← α tiene un fallo infinito. Ejemplo 18 Considere el siguiente programa: en(c, b) en(b, a) La meta ← en(X,Y ), ¬en(Z, X) tiene una refutación-SLDNF con la substitución computada {X/c,Y/b}: 64 5 Negación G = ← en(X,Y ), ¬en(Z, X). G0 = ← en(X,Y ). α0 = en(c, b). θ0 = {X/c,Y /b} G1 = ¬en(Z, X)θ0 = ← en(Z, c) α1 = FF θ1 = ε G2 = 2 θ = θ0 θ1 = {X/c,Y /b} En cambio, si la función de selección hubiera computado las cláusulas de abajo hacı́a arriba α0 = en(b, a) la derivación hubiera sido fallida (a ustedes probarlo). Como es de esperarse, la resolución-SLDNF es consistente, después de todo, la resolución-SLD y la NAF son consistentes. Teorema 7 (Consistencia de la resolución-SLDNF) Sea ∆ un programa definitivo y ← α1 , . . . , αn una meta general. Si ← α1 , . . . , αn tiene una refutación SLDNF con una substitución computada θ , comp(∆ ) |= ∀(α1 θ , . . . , αn θ ). Sin embargo, la resolución-SLDNF no es completa aunque pudiéramos haber esperado lo contrario. La resolución SLDNF no es completa a pesar de que la resolución-SLD y la NAF si lo son. Un simple contra ejemplo es ← ¬en(X,Y ) que corresponde a la consulta “¿Hay algunos bloques X e Y, tal que X no está en Y?” Uno esperarı́a varias respuestas a esta consulta, por ejemplo, el bloque a no está encima de ningún bloque, etc. Pero la derivación SLDNF de ← ¬en(X,Y ) falla porque la meta ← en(X,Y ) tiene éxito (puede ser demostrada). El problema es que nuestra definición de derivación fallida es demasiado conservadora. El éxito de ← en(X,Y ) no significa necesariamente que no halla un bloque que no esté en otro bloque, sólo que existe al menos un bloque que no está en otro. El problema tiene su origen en que la NAF, en contraste con la resolución SLD, es sólo una prueba (test). Recuerden que dada la definición de la resolución SLDNF y la consistencia y completitud de la NAF, tenemos que ¬en(X,Y ) tiene éxito si y sólo si (≡) en(X,Y ) tiene asociado un árbol SLD fallido y finito; o si y sólo si comp(∆ ) |= ∀(¬en(X,Y )). Por lo tanto, la meta general ← en(X,Y ) no debe leerse como una consulta cuantificada existencialmente, sino como una prueba universal: “Para todo bloque X e Y, ¿No está X en Y?”. Esta última consulta tiene una respuesta negativa en el modelo deseado del programa, puesto que el bloque b está en el bloque a. El problema anterior se debe a la cuantificación de las variables en la literal negativa. Si replanteamos la consulta 5.4 Programas Lógicos Generales 65 anterior como ← ¬en(a, b) entonces la resolución SLDNF alcanza una refutación puesto que ← en(a, b) falla con una derivación finita. Algunas veces se asume que la función de selección R permite seleccionar una literal negativa ¬α si la literal α no tiene variables libres o si α tiene asociada una substitución computada vacı́a. Estas funciones de selección se conocen como seguras (safe). 5.4. Programas Lógicos Generales Con los desarrollos anteriores estamos en posición de extender el lenguaje de los programas definitivos para incluir cláusulas que contienen literales tanto positivas como negativas en su cuerpo. Estas fbf se llaman cláusulas generales y a los programas generales que ellas forman, se les conoce a veces como programas lógicos normales. Definición 45 (Cláusula General) Una cláusula general es una fbf de la forma A0 ← α1 , . . . , αn donde A0 es una fbf atómica y α1 , . . . , αn son literales (n ≥ 0). Definición 46 (Programa General) Un programa lógico general es un conjunto finito de cláusulas generales. Ahora podemos extender nuestro programa del mundo de los bloques con las siguientes relaciones: base(X) ← en(Y, X), en la mesa(X). en la mesa(X) ← ¬no en la mesa(X). no en la mesa(X) ← en(X,Y ). en(c, b). en(b, a). La primer cláusula especifica que un bloque es base si está sobre la mesa y tiene otro bloque encima. La segunda cláusula indica que cuando no es cierto que un bloque no está sobre la mesa, entonces está sobre la mesa. La tercera cláusula especifica que un bloque que está sobre otro, no está sobre la mesa. Parece claro, pero la pregunta que deberı́amos hacernos es qué tipo de sistema de prueba queremos para los programas lógicos generales y cuales serán las aproximaciones lógicas a las sutilezas, algunas ya discutidas, introducidas por este tipo de lenguajes. Observen que aunque el lenguaje fue enriquecido, no es posible de cualquier forma que una literal negativa sea consecuencia lógica de un programa dado. La razón es la misma que para los programas definidos, la base de Herbrand de un programa ∆ , B∆ es un modelo de ∆ en el que todas las literales negativas son falsas. Al igual que con los programas definidos, la pregunta es entonces como lograr inferencias 66 5 Negación negativas consistentes. Afortunadamente el concepto de compleción de programa puede aplicarse también a los programas lógicos generales. Ejemplo 19 La compleción de gana(X) ← mueve(X,Y ), ¬gana(Y ) contiene la fbf: ∀X1 (gana(X1 ) ≡ ∃X,Y (X1 = X, mueve(X,Y ), ¬gana(Y ))) Desafortunadamente, la compleción de los programas normales puede ocasionar paradojas. Consideren la cláusula general p ← ¬p, su compleción incluye p ≡ ¬p. La inconsistencia del programa terminado se debe a que p/0 está definida en términos de su propio complemento. Una estrategia de programación para evitar este problema consiste en componer los programas por capas o estratos, forzando al programador a referirse a las negaciones de una relación hasta que ésta ha sido totalmente definida. Se entiende que tal definición se da en un estrato inferior a donde se presenta la negación. En la definición del programa estratificado usaremos ∆ p para referirnos al subconjunto de cláusulas en ∆ que tienen a p como cabeza. Definición 47 (Programa Estratificado) Un programa general ∆ se dice estratificado si y sólo si existe al menos una partición ∆1 ∪ · · · ∪ ∆n de ∆ tal que : 1. Si p(. . . ) ← q(. . . ), · · · ∈ ∆i entonces ∆ q ⊆ ∆1 ∪ · · · ∪ ∆i ; 2. Si p(. . . ) ← ¬q(. . . ), · · · ∈ ∆i entonces ∆ q ⊆ ∆1 ∪ · · · ∪ ∆i−1 . Por ejemplo, el siguiente programa está estratificado: ∆2 : base(X) ← en(Y, X), en la mesa(X). en la mesa(X) ← ¬no en la mesa(X). ∆1 : no en la mesa(X) ← en(X,Y ). en(c, b). en(b, a). La compleción de un programa estratificado es siempre correcta (Apt, Blair y Walker, 1988). Sin embargo, observen que determinar si un programa es estratificado o no, es decidible; pero determinar si la compleción de un programa es o no decidible, es incorrecto. Por lo tanto, hay programas generales no estratificados, cuya terminación es consistente. 5.5 Resolución SLDNF para programas generales 5.5. 67 Resolución SLDNF para programas generales Hemos revisado el caso de la resolución-SLDNF entre programas definitivos y metas generales. Informalmente podemos decir que la resolución-SLDNF combina la resolución SLD con los siguientes principios: 1. ¬α tiene éxito si ← α tiene un árbol-SLD finito que falla. 2. ¬α falla finitamente si y sólo si ← α tiene una refutación-SLD. El paso de programas definitivos a programas generales, es complicado. Para probar ¬α, debe de existir un árbol finito fallido para ← α. Tal árbol puede contener nuevas literales negativas, las cuales a su vez deben tener éxito o fallar finitamente. Esto complica considerablemente la definición de la resolución-SLDNF para programas generales. Por ejemplo, es posible llegar a situaciones paradójicas cuando los predicados están definidos en términos de sus propios complementos. Consideren el programa no estratificado: α ← ¬α Dada la meta inicial ← α, se puede construir una derivación ← α ← ¬α. La derivación puede extenderse hasta una refutación si ← α falla finitamente. De manera alternativa, si ← α tiene una refutación, entonces la derivación falla. Helas! esto es imposible pues la meta ← α no puede tener una refutación y fallar finitamente al mismo tiempo. En lo que sigue, definiremos las nociones de derivación-SLDNF y árbol-SLDNF, de manera similar a la derivación-SLD y a los arboles-SLD. La idea se concreta en el concepto de bosque-SLDNF: un conjunto de árboles-SLDNF cuyos nodos está etiquetados con metas generales. Definición 48 (Bosque SLDNF) Sea ∆ un programa general, G0 una meta general, y R una función de selección. El bosque SLDNF de G0 (usando ∆ y R) es el bosque más pequeño, tal que: 1. G0 es la raı́z del árbol. 2. Si G es un nodo en el bosque cuya literal seleccionada es positiva, entonces para cada cláusula α tal que G0 puede ser derivada de G y α (con MGU θ ), G tiene un hijo etiquetado G0 . Si no existe tal cláusula, entonces G tiene un hijo etiquetado FF (falla finita); 3. Si G es un nodo del bosque cuya literal seleccionada es de la forma ← ¬α (G es de la forma ← α1 , . . . , Li−1 , αi , Li+1 , . . . , αn ), entonces: El bosque contiene un árbol cuyo nodo raı́z es ← α. Si el árbol con raı́z ← α tiene una hoja etiquetada como 2 con la substitución computada vacı́a ε, entonces G tiene un sólo hijo etiquetado FF; Si el árbol con raı́z ← α es finito y tiene todas sus hojas etiquetadas FF, entonces G tiene un sólo hijo etiquetado (con substitución asociada vacı́a ε) como ← α1 , · · · Li−1 , Li+1 , . . . , αn . 68 5 Negación Observen que la literal negativa seleccionada ¬α falla sólo si ← α tiene una refutación con la substitución computada vacı́a ε. Como veremos más adelante, esta condición que no era necesaria cuando definimos la resolución-SLDNF para programas definitivos, es vital para la correctez de esta resolución en los programas generales. Los arboles del bosque-SLDNF son llamados arboles-SLDNF completos; y la secuencia de todas las metas en una rama de un árbol-SLDNF con raı́z G es llamada derivación-SLDNF completa de G (bajo un programa ∆ y una función de selección R). El árbol etiquetado por G0 es llamado árbol principal. Un árbol con la raı́z ← α es llamado árbol subsidiario si ¬α es una literal seleccionada en el bosque (el árbol principal puede ser a su vez subsidiario). Ejemplo 20 Consideren el siguiente programa general estratificado ∆ : base(X) ← en(Y, X), en la mesa(X). en la mesa(X) ← ¬no en la mesa(X). no en la mesa(X) ← en(X,Y ). encima(X,Y ) ← en(X,Y ). encima(X,Y ) ← en(X, Z), encima(Z,Y ). en(c, b). en(b, a). El bosque-SLDNF para la meta ← base(X) se muestra en la figura 5.2. El árbol principal contiene una derivación fallida y una refutación con la substitución computada {X/a}. Las ramas de un árbol-SLDNF en un bosque-SLDNF representan todas las derivaciones-SLDNF completas de su raı́z, con base en la función de selección dada. Hay cuatro clases de derivaciones-SLDNF completas: 1. 2. 3. 4. derivaciones infinitas; derivaciones finitas fallidas (terminan en FF); refutaciones (terminan en 2); y derivaciones plantadas (si ninguno de los casos anteriores aplica). Ejemplo 21 Consideren el siguiente programa: termina(X) ← ¬ciclo(X). ciclo(X) ← ciclo(X). El bosque-SLDNF para el ejemplo anterior se muestra en la figura 5.3. El bosque incluye una derivación plantada para termina(X) y una derivación infinita para ciclo(X). Esto ilustra una de las razones por las cuales una derivación se planta: uno de sus arboles subsidiarios contiene sólo derivaciones fallidas o infinitas. 5.5 Resolución SLDNF para programas generales 69 base(X). ← en(Y0 , X), en la mesa(X). ← en la mesa(b). ← en la mesa(a). ← ¬no en la mesa(b). ← ¬en la mesa(a). FF 2 ← no en la mesa(b). ← no en la mesa(a). ← en(b,Y0 ). ← en(a,Y0 ). 2 FF Figura 5.2 Bosque-SLDNF para la meta ← base(X). ← ciclo(X). ← paro(X). ← ciclo(X). ← ¬ciclo(X). ∞ Figura 5.3 Bosque-SLDNF para la meta ← paro(X). El siguiente programa también conduce a una derivación plantada (ciclo en el cómputo de la negación): parado ja(X) ← ¬ok(X). ok(X) ← ¬parado ja(X). Intenten construir el bosque-SLDNF de este programa y observaran también que en este caso, la árbol principal es a su vez un árbol subsidiario. La última razón para que una derivación quede plantada es ilustrada por el siguiente programa: 70 5 Negación top(X) ← ¬bloqueado(X). bloqueado(X) ← en(Y, X). en(a, b). Es evidente que top(a) deberı́a poder derivarse de este programa. Sin embargo, el árbol-SLDNF de la meta ← top(X) no contiene refutaciones. De hecho, esta meta se planta aún cuando ← bloqueado(X) tiene una refutación. La razón de esto es que ← bloqueado(X) no tiene ninguna derivación que termine con una substitución computada vacı́a. A la meta ← ¬top(X), Prolog no responde b, sino que ¡todos los bloques no están en el tope de la pila! Esto se debe a la implementación de la mayorı́a de los Prolog. La definición que dimos aquı́ de resolución-SLDNF es correcta. Teorema 8 (Correctez de la resolución-SLDNF) Sea ∆ un programa general y ← α1 , . . . , αn una meta general. Entonces: Si ← α1 , . . . , αn tiene una substitución de respuesta computada θ , entonces comp(∆ ) |= ∀(α1 θ ∧ · · · ∧ αn θ ). Si ← α1 , . . . , αn tiene un árbol-SLDNF finito que falla, entonces comp(∆ ) |= ∀(¬(α1 ∧ · · · ∧ αn )). La definición de bosque-SLDNF no debe verse como una implementación de la resolución-SLDNF, sólo representa el espacio ideal de computación donde la correctez puede ser garantizada. Capı́tulo 6 Corte y Aritmética Resumen La computación de un programa lógico requiere la construcción y recorrido de un árbol-SLD. Esto no es necesariamente la forma más eficiente de computación, por lo que en esta clase abordaremos dos extensiones de la programación lógica, implementadas en ISO Prolog, para acelerar las computaciones realizadas: el corte y la aritmética. Por simplicidad, la presentación se basa en los programas lógicos definitivos, aunque los temas discutidos son aplicables a las derivaciones y arboles-SLDNF. 6.1. Corte: podando el árbol-SLD El árbol-SLD de una meta definitiva puede tener muchas ramas que conducen al fallo de la meta y muy pocas, ó una sola rama, que conducen al éxito. Por ello, el programador podrı́a querer incluir información de control en sus programas, para evitar que el intérprete construya ramas fallidas. Observen que esta meta-información se basa en la semántica operacional del programa, por lo que el programador debe saber como se construyen y se recorren los arboles-SLD. El predicado !/0 denota la operación de corte, y puede utilizarse como una literal en las metas definitivas. Su presencia impide la construcción de ciertos sub-arboles. Un intérprete de Prolog recorre los nodos de un árbol-SLD primero en profundidad. El orden de las ramas corresponde al orden textual de las cláusulas en el programa. Cuando una hoja es alcanzada, el proceso de backtracking es ejecutado. El proceso termina cuando no es posible hacer backtracking (todos los sub-arboles de la raı́z del árbol han sido visitados). Ejemplo 22 Asumamos el siguiente programa que define que el padre de una persona es su antecesor hombre: 71 72 6 Corte y Aritmética padre(X,Y ) ← progenitor(X,Y ), hombre(X). progenitor(ben jamin, antonio). progenitor(maria, antonio). progenitor(samuel, ben jamin). progenitor(alicia, ben jamin). hombre(ben jamin). hombre(samuel). El árbol-SLD de la meta ← padre(X, antonio) se muestra en la figura 6.1. Bajo la función de selección implementada en Prolog, encontrará la solución X/ben jamin. El intento por encontrar otra solución con X/maria, mediante el backtracking, fallará puesto que maria no satisface el predicado hombre/1. ← padre(X, antonio) ← progenitor(X, antonio), hombre(X). hombre(ben jamin). ← hombre(maria) 2 Figura 6.1 Arbol de derivación-SLD para la meta ← padre(X, antonio) Para detallar la semántica del corte, es necesario introducir algunos conceptos auxiliares. En un árbol-SLD, cada nodo ni corresponde a una meta Gi de una derivación-SLD y tiene un átomo seleccionado asociado αi : G0 α0 G1 . . . Gn−1 αn−1 Gn Asumamos que para cierto nodo nk , αk no es una sub-meta de la meta inicial. Entonces αk es un átomo βi del cuerpo de una cláusula de la forma β0 ← β1 , . . . , βi , . . . , βn cuya cabeza β0 unifica con la sub-meta seleccionada en algún nodo n0< j<k , es decir un nodo entre la raı́z del árbol y el nodo nk . El nodo n j se conoce como el origen de αk y se denota como origen(αk ). El predicado de corte ! se procesa como un átomo ordinario situado en el cuerpo de una cláusula. Sin embargo, cuando el corte es seleccionado para computar la resolución, éste tiene éxito inmediatamente (con la substitución vacı́a ε como resultado). El nodo donde ! fue seleccionado es llamado el nodo de corte. Un nodo de corte puede ser visitado nuevamente durante el backtracking. En este caso, el curso normal del recorrido del árbol es alterado (por definición el recorrido continua 6.1 Corte: podando el árbol-SLD 73 en el nodo superior a origen(!). Si el corte ocurre en la meta inicial, la ejecución simplemente termina. Ejemplo 23 La formulación del problema padre, nos dice que a lo más existe una solución para nuestra meta. cuando la solución se encuentra, la búsqueda puede deternerse pues ninguna persona tiene más de un padre. Para forzar esta situación, el predicado de corte se agrega al final de padre/2: padre(X,Y ) ← progenitor(X,Y ), hombre(X), !. Observen que el programa modificado en el ejemplo anterior sólo puede computar un elemento de la relación padre/2. El corte detendrá la búsqueda después de encontrar la primer respuesta para la meta ← padre(X,Y ). El origen del corte es la raı́z del árbol, por lo que la búsqueda termina después de hacer backtracking al nodo de corte. La otra rama del árbol no es recorrida. El árbol-SLD del programa que incluye el corte se muestra en la figura 6.2. ← padre(X, antonio) ← progenitor(X, antonio), hombre(X), !. hombre(ben). ←! 2 Figura 6.2 Arbol de derivación-SLD para la meta ← padre(X, antonio) con las ramas fallidas podadas. Observen que la versión modificada con el corte, no puede usarse para computar más de un elemento de la relación “es padre de”. El corte detendrá la búsqueda después de encontrar la primer respuesta a la meta definitiva. A partir de la definición del corte, se sigue que los efectos del operador son: 1. Divide el cuerpo de la meta en dos partes, separando la ejecución de la reconsideración – después de éxito de !/0 , no es posible hacer backtracking hacı́a las literales a la izquierda del corte. Sin embargo, a la derecha del corte todo funciona de manera usual. 2. Poda las ramas sin explorar directamente bajo origen(!). En otras palabras, no habrá más intentos de unificar la sub-meta seleccionada de origen(!) con el resto de las cláusulas del programa. 74 6 Corte y Aritmética El corte es controvertido. La intención al introducir el corte, es poder controlar la ejecución de los programas, sin cambiar su significado lógico. Por tanto, la lectura lógica del corte es “verdadero”. Operacionalmente, si el corte remueve sólo ramas fallidas del árbol-SLD, no tiene influencia en el significado lógico de un programa. Pero el corte puede remover también ramas exitosas del árbol-SLD, atentando contra la completitud de los programas definitivos, o la correctez de los programas generales. Ejemplo 24 Es bien sabido que los padres de un recién nacido están orgullosos. La proposición puede representarse con la siguiente cláusula definitiva: orgulloso(X) ← padre(X,Y ), recienNacido(Y ). consideren las siguiente cláusulas adicionales: padre(X,Y ) ← progenitor(X,Y ), hombre(X). progenitor( juan, maria). progenitor( juan, cristina). hombre( juan). recienNacido(cristina). La respuesta a la meta ← orgulloso( juan) es “Si”, puesto que como describimos, juan es padre de cristina, que es un recién nacido. Ahora, si remplazamos la primera cláusula, con su versión que utiliza corte: padre(X,Y ) ← progenitor(X,Y ), hombre(X), !. Y preguntamos nuevamente a Prolog, si ← orgulloso( juan). la respuesta será “No”. Esto se debe a que la primer hija de juan en el programa es maria. Una vez que esta respuesta se ha encontrado, no habrá más intentos de satisfacer la meta en origen(!). No se considerarán más hijos de juan en la solución computada. El programa del ejemplo anterior se ha vuelto incompleto, algunas respuestas correctas no pueden ser computadas. Más grave aún es el caso de las metas generales, donde se puede llegar a resultados incorrectos, por ejemplo, ← ¬orgulloso( juan) tendrı́a éxito en la versión de nuestro programa que utiliza corte. Hasta ahora hemos distinguido dos usos del corte: eliminar ramas fallidas en el árbol-SLD; y podar ramas exitosas. Eliminar ramas fallidas se considera una práctica sin riesgo, porque no altera las respuestas producidas durante la ejecución de un programa. Tales cortes se conocen como cortes verdes. Sin embargo, este uso del operador corte, esta ligado al uso particular de un programa. Como se ilustra en 6.1 Corte: podando el árbol-SLD 75 los ejemplos anteriores, para algunas metas, el operador solo eliminará ramas fallidas; pero para otras podará ramas exitosas. Cortar ramas exitosas se considera una práctica de riesgo. Por eso, tales cortes se conocen como cortes rojos. Ejemplo 25 Consideremos un ejemplo de corte verde. Si en el ejemplo anterior maria es una recién nacida, agregarı́amos la cláusula recienNacido(maria) a nuestro programa. Entonces la meta ← orgulloso(X) nos dirı́a que X/X/ juan está orgulloso. Esto es, juan tiene una doble razón para estar orgulloso. Pero a nosotros nos basta con saber sólo una vez, que orgulloso está juan. Para evitar que Prolog nos de la respuesta dos veces, definirı́amos: orgulloso(X) ← padre(X,Y ), recienNacido(Y ), !. Ejemplo 26 Ahora consideren un ejemplo de corte rojo: min(X,Y, X) ← X < Y, !. min(X,Y,Y ). Aparentemente nuestro programa es correcto. De hecho, el programa responderı́a de manera correcta a metas como ← min(2, 3, X) respondiendo que “Si” para X/2; y para ← min(3, 2, X) responderı́a que “Si” para X/2. Sin embargo el programa no es correcto. Consideren la meta ← min(2, 3, 3) y verán que Prolog responderı́a “Si”. La razón de esto es que la segunda cláusula dice: el menor de X e Y es siempre Y . El corte está eliminando algunas ramas fallidas, que serı́an útiles en la definición de min. La definición correcta, usando corte, serı́a: min(X,Y, X) ← X < Y, !. min(X,Y,Y ) ← X ≥ Y. Un comentario final. El corte puede usarse para implementar la negación en Prolog. Consideren las siguientes cláusulas donde f ail es un predicado de Prolog que carece de definición y no puede ser definido por el usuario: not(estudiante(X)) ← estudiante(X), !, f ail. not(estudiante(X). Esta definición descansa enteramente en la semantica operacional de Prolog. Esto es, las sub-metas se deben resolver de izquierda a derecha, y las cláusulas se buscan en el orden en que aparecen en el texto del programa. Si queremos saber si juan no es un estudiante, le meta a adoptar es ← not(estudiante( juan)). Ahora, hay dos casos a considerar: Si la meta ← estudiante( juan) tiene éxito, el operador de corte eliminará la segunda cláusula y la meta original fallará; Si la meta ← not(estudiante( juan)) falla, la segunda cláusula será intentada en el backtracking y la meta negada tendrá éxito. Podemos definir not/1 haciendo uso del meta-predicado estándar de Prolog call/1: 76 6 Corte y Aritmética not(X) ← call(X), !, f ail. not(X). El argumento a call/1 debe ser un átomo de base, de otra forma, el cómputo produce substituciones y la implementación resulta lógicamente incorrecta. Usar cortes aleatoriamente, para intentar obtener respuestas correctas a un problema, es una de las fuentes principales de errores entre los novatos de la programación lógica. Antes de intentar usar un corte, intenten escribir programas lógicamente correctos. 6.2. Aritmética Hemos demostrado que los programas definitivos pueden describir cualquier relación computable. Esto es, cualquier máquina de Turing puede codificarse como un programa lógico definitivo. Esto significa que, desde el punto de vista teórico, la programación lógica es tan expresiva como otros paradigmas de programación. La resolución y la búsqueda exhaustiva, proveen una herramienta universal de computación. Desafortunadamente, desde el punto de vista práctico, ésta no es la mejor forma de computar todo. Consideren las operaciones aritméticas sobre los números naturales. Existen implementaciones en hardware extremadamente eficientes de tales operaciones. Ası́ que es deseable que Prolog tenga acceso a las operaciones del procesador y su aritmética de máquina. El problema es ¿Cuando es posible hacer cosas parecidas sin destruir la naturaleza declarativa de los programas lógicos? Observen primero que las operaciones como suma/2 y multiplicacion/2 pueden describirse fácilmente en un programa definitivo. Los números naturales pueden describirse mediante términos de base. Una forma estándar de hacer esto es utilizar la constante 0 para representar el cero, y el functor unario s/1 para representar el sucesor de un número. Los números naturales consecutivos están representados por: 0, s(0), s(s(0)), . . . Las operaciones de adición y multiplicación son funciones binarias sobre los números naturales. Los programas lógicos proveen únicamente un formalismo para representar relaciones. Sin embargo, una función binaria puede verse como una relación ternaria consistente en todas las tripletas hX,Y, Zi tal que Z es el resultado de aplicar la función a X e Y . Ahora, es bien sabido que las operaciones de adición y multiplicación se caracterizan por los axiomas de Peano: 6.2 Aritmética 77 0+X = X s(X) +Y = s(X +Y ) 0×X = 0 s(X) ×Y = (X ×Y ) +Y Estos axiomas relacionan argumentos y resultados de las operaciones. En forma de programa lógico definitivo, se formuları́an como sigue: suma(0, X, X). suma(s(X),Y, s(Z)) ← suma(X,Y, Z). mult(0, X, 0). mult(s(X),Y, Z) ← mult(X,Y,W ), suma(W,Y, Z). El programa puede usarse, por ejemplo, para sumar dos y tres: ← suma(s(s(0)), s(s(s(0))), X) que darı́a como resultado “si” para X/s(s(s(s(s(0))))). Un árbol de refutación-SLD es construido para obtener este resultado. El programa puede usarse también para computar resta y una forma limitada de división. Por ejemplo: ← suma(X, s(s(0)), s(s(s(0)))) representa la resta de tres menos dos, dando como resultado “Si” para X = s(0). Cuando comparamos estos ejemplos con la práctica común en programación, resulta evidente que: La representación de los números naturales como términos de base no es adecuada para los humanos. Las computaciones de la aritmética no hacen uso del hardware, por lo que resultan lentas: Sumar X e Y requiere X + 1 llamada a suma. No es posible construir expresiones aritméticas, puesto que suma/3 y mult/3 representan relaciones. El primer problema se puede resolver fácilmente introduciendo arreglos sintácticos: sn (0) = n. También se conocen técnicas para compilar expresiones aritméticas a código ensamblador. El principal problema es cómo incorporar expresiones aritméticas sin destruir el significado declarativo de los programas. Asumamos que las expresiones aritméticas aparecerán como términos en los programas definitivos. Las respuestas de estos programas deberán tomar en consideración la equivalente entre expresiones aritméticas. Por ejemplo, consideren la siguiente regla para computar impuestos: si el ingreso anual es mayor que 150000 dolares, entonces el impuesto es 30 %, en otro caso se cobrará el 25 % del ingreso menos 30000 dolares. 78 6 Corte y Aritmética impuesto(Ingresos, 0,5 ∗ Ingresos) ← Ingresos > 150000. impuesto(Ingresos, 0,25 ∗ (Ingresos − 30000)) ← Ingresos ≤ 150000. Por lo tanto, una persona que gana 130000 dolares deberı́a obtener para la meta ← impuesto(130000, 25000) una respuesta de “Si”. Pero estas reglas no pueden usarse para encontrar una refutación a la meta, ya que ninguna cabeza de regla unifica con la sub-meta de la meta. La razón es que la unificación estándar es muy débil, como para darse cuenta que 25000 y 0.25*(130000-30000) son equivalentes. Por lo tanto, la equivalencia debe describirse mediante axiomas de igualdad para la aritmética, los cuales no han sido incluidos en el programa anterior. De esta discusión se sigue que es necesaria una extensión al concepto de programa lógico. Para nuestro ejemplo, el programa deberı́a consistir de dos partes: un conjunto de cláusulas definitivas P, y un conjunto de axiomas de igualdad E, describiendo la equivalencia entre los términos. Este tipo de programas ha sido estudiado en la literatura, donde el resultado más importante es el concepto de unificación generalizada, asociada a una teorı́a de igualdad E y llamada E-unificación. Lo que sigue es una breve introducción a este tema. Una teorı́a de igualdad en cláusulas definitivas es un conjunto (posiblemente infinito) de cláusulas definitivas, donde cada átomo es de la forma s = t y s y t son términos. Algunas veces, la forma de las cláusulas se restringe a hechos. Un programa definitivo con igualdad es un par P, E donde P es un programa definitivo, que no contiene ocurrencias del predicado = /2 y E es una teorı́a de igualdad en cláusulas definitivas. Sea E una teorı́a de igualdad en cláusulas definitivas. Una substitución θ es un E-unificador de los términos s y t, si y sólo si sθ = tθ es una consecuencia lógica de E. Ejemplo 27 Sea E una teorı́a de igualdad que expresa las equivalencias propias de las expresiones aritméticas. Consideren las expresiones: t1 := (2 × X) + 1 t2 := Y + 2 Por ejemplo, la substitución θ = {Y /(2 × X − 1)} es un E-unificador de t1 y t2 . Ahora, para un programa dado P, E y una meta ← α1 , . . . , αn la refutación de la meta puede construirse de la misma manera que para los programas definitivos, con la diferencia de que utilizaremos E-unificación, en lugar de la unificación normal. Encontrar E-unificadores puede verse como la resolución de ecuaciones en un álgebra definida por los axiomas de igualdad. Se sabe que el problema de la Eunificación es en general indecidible. Aún siendo decidible para alguna teorı́a E, pueden existir muchas soluciones a una ecuación dada. La situación donde existe un unificador más general, suele ser rara. Todo esto significa que, aún cuando es posible construir E-unificadores, una nueva fuente de indeterminismo se introduce con ellos. 6.2 Aritmética 79 Asumamos ahora que una teorı́a de igualdad E describe todas las funciones externas, incluidas las operaciones aritméticas, usadas en un programa lógico. Esto significa que para cualquier par de términos de base s y t, cuyos functores principales denoten funciones externas, la fórmula s = t es una consecuencia lógica de E, si y sólo si, la invocación de s regresa el mismo resultado que la invocación de t. Para términos base, la E-unificación es decidible. Esto puede ser explotado de la siguiente manera: cuando una llamada a una función externa es encontrada como término a Eunificar, es invocada y su forma reducida es unificada por el procedimiento normal. Observen que las funciones externas sólo pueden ser invocadas sobre argumentos de base. Si algunas variables de la función externa no tienen valores, la llamada externa provocará un error y no se encontrará un E-unificador. Esta idea es incorporada en el ISO Prolog. Los enteros son representados por numerales como 0, 1, 2006, etc. También existe un soporte limitado para números reales como 3.14, 0.333, etc. Lógicamente, los numerales son constantes. Un número limitado de functores con notación infija están predefinidos en Prolog: +, -, *, / (división en reales), // (división entera), mod, etc. Dos predicados predefinidos se ocupan de la E-unificación. El predicado =:= /2 checa si dos términos de base son E-unificables. Por ejemplo, la meta ← 2 ∗ 3 =:= 2 + 4 responde “Si” con la substitución vacı́a computada. Si los argumentos no son términos de base, Prolog aborta la ejecución del programa con un mensaje de error. El predicado is/2 unifica el primer argumento con la forma reducida del segundo. Por ejemplo ← X is 2 + 2 responde “Si” con la substitución {X/4}. El primer argumento puede no ser una variable, en lo que constituye un caso especial de la E-unificación =:= /2. Por ejemplo, ← X + 1 is 2 + 3 falla aunque exista un Eunificador {X/4}. Otros predicados estándar incluyen = \ = /2 que checa cuando dos términos no son E-unificables. El lenguaje también provee predicados para comparar números, como >,<,≤ y ≥. Parte II Prolog Capı́tulo 7 Introducción a Prolog Este capı́tulo constituye una guı́a rápida al lenguaje prolog. 83 Capı́tulo 8 Estrategias básicas de resolución de problemas Resumen En este capı́tulo revisaremos los conceptos de espacio de soluciones y búsquedas en espacios de soluciones. Se revisara la representación de estos espacios en Prolog y se ejemplificará el uso de estrategias como las búsquedas primero en profundidad, primero en amplitud y guiadas por funciones de utilidad. 8.1. Introducción Esta sesión la dedicaremos a estudiar un esquema general de representación de problemas y sus soluciones, ampliamente utilizado en la Inteligencia Artificial. Consideremos el ejemplo mostrado en la figura 8.1. El problema a resolver consiste en encontrar un plan para colocar los cubos en una configuración determinada, partiendo de una configuración inicial. Sólo un bloque puede moverse a la vez y las acciones del brazo son del tipo “pon A en la mesa”, “pon B en C”, etc. Dos conceptos aparecen en esta descripción: i) Situaciones o estados del problema; y ii) acciones, o movimientos legales, que transforman un estado del problema en otro. C ? A A B B C Figura 8.1 Un problema de ordenamiento de bloques. Estados y acciones configuran un grafo dirigido conocido como espacio de estados (Fig. 8.2. El problema de encontar un plan para acomodar los cubos es equivalente a encontrar un camino en este grafo, entre un nodo representado el estado ini85 86 8 Estrategias básicas de resolución de problemas cial del problema y un nodo representando la solución final, un nodo meta. ¿Cómo podemos representar tal grafo en Prolog? Figura 8.2 Espacio de estados para el problema de ordenamiento de bloques. El espacio de estados será representado por una relación s(X,Y ) que será verdadera si existe un movimiento válido en el espacio de estados del nodo X al nodo Y . El nodo Y recibe el nombre de sucesor de X. Si existe un costo asociado a las acciones esto puede representarse por un tercer argumento de s, s(X,Y,Costo). Esta relación puede ser especificada extensionalmente por un conjunto de hechos. Sin embargo, para cualquier problema interesante, esto es irrealizable. La relación s es normalmente definida intensionalmente mediante un conjunto de reglas que computan el sucesor de un nodo dado. Otro detalle importante tiene que ver con la representación de los estados del problema, los nodos. La representación debe de ser compacta y permitir la computación eficiente de los nodos sucesores; y posiblemente el costo asociado a las acciones. Tomemos como ejemplo el mundo de los bloques. Cada estado del problema puede ser representado por una lista pilas. Cada pila a su vez puede ser representada por una lista de bloques. El tope de cada pila es el primer elemento de cada lista de bloques. La pila vacı́a está representada por la lista vacı́a. Ası́, el estado inicial mostrado en la figura 8.1 es la lista: [[c,b,a],[],[]] (suponemos, que en la mesa sólo hay espacio para 3 pilas de bloques). Una meta es cualquier arreglo con los bloques en el orden deseado. Existen tres soluciones en este caso: [[a,b,c],[],[]], [[],[a,b,c],[]] ó [[],[],[a,b,c]]. La relación sucesor puede programarse de acuerdo a la siguiente regla: el Estado2 es sucesor de Estado1 si hay dos pilas Pila1 y Pila2 en Estado1 y el tope de la pila Pila1 puede moverse a Pila2. Esto se traduce a Prolog como: 8.2 Búsqueda primero en profundidad 1 2 3 87 s(Pilas, [Pila1, [Tope1|Pila2] | OtrasPilas ]) :quitar([Tope1|Pila1], Pilas, Pilas1), quitar(Pila2, Pilas1, OtrasPilas). 4 5 6 7 quitar(X, [X|Ys], Ys). quitar(X, [Y|Ys], [Y|Ys1]) :quitar(X,Ys,Ys1). La relación s nos permite verificar si un nodo es sucesor de otro, por ejemplo: 1 2 3 4 ?- s([[b],[a,c],[]],[[],[b,a,c],[]]). Yes ?- s([[b],[a,c],[]],[[],[a,b,c],[]]). No Para representar los estados meta usamos: 1 2 meta(Estado) :member([a,b,c],Estado). Un predicado solucion/2 se usa para plantear las metas, por ejemplo: solucion([[c,a,b],[],[]],Sol). La solución será encontrada buscando en el espacio de estados del problema. 8.2. Búsqueda primero en profundidad Dada la formulación de un problema en términos de su espacio de estados, existen diversas estrategias para encontrar un camino solución. Dos estrategias básica son las búsquedas primero en profundidad y primero en amplitud. En esta sección implementaremos la búsqueda primero en profundidad. Comenzaremos con una idea simple. Para encontrar un camino solución Sol, de un nodo dado N a un nodo meta: Si N es un nodo meta, entonces Sol = [N], o Si existe un nodo sucesor N1 tal que existe un camino Sol1 de N1 al nodo meta, entonces Sol = [N|Sol1]. Lo cual traduce a Prolog como: 1 2 solucion(N,[N]) :meta(N). 3 4 5 6 solucion(N, [N|Sol1]) :s(N,N1), solucion(N1,Sol1). 88 8 Estrategias básicas de resolución de problemas De forma que para computar la solución al problema de los bloques, preguntamos a Prolog: ?- solucion([[c,b,a],[],[]],Sol). Sol = [[[c, b, a], [], []], [[b, a], [c], []], [[a], [b, c], []], [[], [a, b, c], []]] Yes La solución se computa como sigue. En un principio, el estado inicial N = [[c, b, a][][]], por lo que el programa se pregunta si N es una meta. La cláusula meta/1 funciona verificando si la solución [a, b, c] es miembro del estado N. Como esta meta falla, Prolog intentará satisfacer su meta inicial con la segunda cláusula solucion/2. Esto implica generar un sucesor de N (llamada a s(N, N1)). Ası́ que se computa N1 = [[b, a], [c], []] y se verifica si esto es una solución. Como la meta falla, se genera un sucesor de N1 y ası́ hasta llegar a [[], [a, b, c], []]. Este proceso puede seguirse detalladamente utilizando el traceador gráfico de SWI-Prolog. Para ello invoquen la meta guitracer. Al trazar una función verán una ventana como la mostrada en la figura 8.3. La ventana superior izquierda muestra las substituciones computadas, la derecha las pilas formadas, y la inferior muestra el código del programa que está siendo trazado. Figura 8.3 Traza gráfica de SWI-Prolog. 8.3 Búsqueda primero en amplitud 89 Una primera mejora a este algoritmo, consiste en evitar que los nodos visitados vuelvan a ser expandidos, evitando ası́ caer en ciclos. La idea es llevar un registro de los nodos visitados. El resultado se da del nodo final hacı́a el estado inicial: 1 2 solucion2(Nodo,Sol) :primeroProfundidad([],Nodo,Sol). 3 4 5 primeroProfundidad(Camino, Nodo, [Nodo|Camino]) :meta(Nodo). 6 7 8 9 10 primeroProfundidad(Camino, Nodo, Sol) :s(Nodo,Nodo1), not(member(Nodo1, Camino)), primeroProfunidad([Nodo|Camino],Nodo1,Sol). Finalmente, para evitar caer en búsquedas infinitas sobre ramas no ciclicas, es posible establecer un limite a la profunidad de la búsqueda. Para ello definiremos primeroProfundidad2/3, donde el tercer argumento es la profunidad máxima de la búsqueda. 1 2 solucion3(Nodo,Sol,MaxProf) :primeroProfundidad2(Nodo,Sol,MaxProf). 3 4 5 primeroProfundidad2(Nodo,[Nodo],_) :meta(Nodo). 6 7 8 9 10 11 primeroProfundidad2(Nodo,[Nodo|Sol],MaxProf):MaxProf > 0, s(Nodo,Nodo1), Max1 is MaxProf-1, primeroProfundidad2(Nodo1,Sol,Max1). 8.3. Búsqueda primero en amplitud En contraste con la búsqueda primero en profundidad, la estrategia de búsqueda primero en amplitud elige visitar primero los nodos que están más cercanos a la raı́z, por lo que el árbol de búsqueda crece más en amplitud, que en profundidad. Esta estrategia de búsqueda es más complicada de programar. La razón de ello es que debemos mantener un conjunto de nodos candidatos alternativos, no únicamente un nodo como lo hacı́amos al buscar en profundidad. Además, debemos mantener los caminos, si queremos obtenerlos como parte de la solución computada. De forma que: ?- primeroEnProfundidad(Caminos,Sol). 90 8 Estrategias básicas de resolución de problemas es verdadera, si y sólo si algún camino miembro del conjunto de candidatos Caminos, puede extenderse hasta un nodo meta. Sol es el camino solución. El conjunto Caminos será representado como listas de caminos, donde cada camino se representará como una lista de nodos en el orden inverso en que fueron visitados. Esto es, la cabeza de la lista que representa un camino tendrá el último nodo generado; y el último nodo en la lista será el estado inicial de la búsqueda. Al iniciar Caminos tiene un sólo camino candidato: [[NodoInicial]]. El algoritmo de la búsqueda primero en amplitud puede describirse como sigue, dado un conjunto de caminos candidatos: Si el primer camino contiene un nodo meta como su cabeza, entonces esta es la solución al problema. De otra forma Eliminar el primer camino del conjunto de caminos candidatos y generar el conjunto de todas las posibles extensiones de un paso de este camino. Agregar este conjunto de extensiones al final del conjunto de candidatos. Ejecutar la búsqueda primero en amplitud en este nuevo conjunto de caminos candidatos. Para generar las extensiones de un sólo paso, dado un camino, podemos usar el predicado predefinido bago f Veamos el programa: 1 2 %% % solucion(Inicio,Sol) Sol es un camino (en orden inverso) %% % de Inicio a una meta 3 4 5 solucion(Inicio,Sol) :primeroEnAmplitud([[Inicio]],Sol). 6 7 8 %% % primeroEnAmplitud([Camino1,Camino2,...],Sol) Sol es una %% % extensión hacı́a la meta de alguno de los caminos 9 10 11 primeroEnAmplitud([[Nodo|Camino]|_],[Nodo|Camino]) :meta(Nodo). 12 13 14 15 16 primeroEnAmplitud([Camino|Caminos],Sol) :extender(Camino,NuevosCaminos), append(Caminos,NuevosCaminos,Caminos1), primeroEnAmplitud(Caminos1,Sol). 17 18 19 20 21 22 extender([Nodo|Camino],NuevosCaminos) :bagof([NuevoNodo,Nodo|Camino], (s(Nodo,NuevoNodo), not(member(NuevoNodo, [Nodo| Camino]))), NuevosCaminos), !. 23 24 %% % Si extender falla, Camino no tiene sucesores (lista vacı́a) 25 26 extender(Camino_,[]). Si aplicamos este programa de búsqueda al programa del mundo de los cubos, obtendremos: 8.3 Búsqueda primero en amplitud 91 ?- solucion([[c,b,a],[],[]],Sol). Sol = [[[], [a, b, c], []], [[a], [b, c], []], [[b, a], [c], []], [[c, b, a], [], []]] Yes Si queremos buscar en el espacio del gráfo de la figura 8.4, codificamos los sucesores y las metas como sigue: 1 2 3 4 5 6 7 8 9 10 s(a,b). s(a,c). s(b,d). s(b,e). s(d,h). s(e,i). s(e,j). s(c,f). s(c,g). s(f,k). 11 12 13 meta(j). meta(f). a d h j b c e f i g k Figura 8.4 Gráfico de un espacio de estados: las metas son f y j. y buscamos las soluciones: ?- solucion(a,Sol). Sol = [f, c, a] ; Sol = [j, e, b, a] ; No Observen que al realizarse la búsqueda primero en amplitud, la primer solución encontrada involucra la meta f más cercana al nodo raı́z. 92 8.4. 8 Estrategias básicas de resolución de problemas Búsqueda primero el mejor Un programa de búsqueda primero el mejor, puede verse como una mejora a la búsqueda primero en amplitud. El algoritmo de primero el mejor comienza también con un nodo inicial y mantiene una lista de caminos candidato. La búsqueda por amplitud siempre elige para expandir el camino candidato más corto y la búsqueda primero el mejor afina esta estrategia. Asumamos que una función costo es definida para los arcos de un espacio de estados de un problema. De forma que c(n, n0 ) es el costo de moverse de un nodo n al nodo n0 en el espacio de estados. Sea el estimador heurı́stico una función f tal que para cada nodo n en el espacio de estados, f (n) estima la “dificultad” de llegar a n. De acuerdo a esto, el nodo más promisorio será aquel que minimice f . Usaremos aquı́ una forma especial de la función f que nos llevará al bien documentado algoritmo A*. f (n) será construida para estimar el costo del mejor camino solución entre un nodo inicial s y un nodo meta, con la restricción de que el camino pase por el nodo n. Supongamos que tal camino existe y que un nodo meta que minimiza su costo es t. Entonces el estimado de f (n) puede calcularse como la suma de dos términos: f (n) = g(n) + h(n) donde g(n) es el estimado del costo de un camino óptimo de s a n; y h(n) es el estimado del costo de un camino óptimo de n a t (Fig. 8.5). s g(n) n n' n'' h(n) t Figura 8.5 Estimado heurı́stico f (n) = g(n) + h(n). Cuando un nodo n es encontrado en el proceso de búsqueda, tenemos la siguiente situación: un camino de s a n debió ser encontrado, y su costo puede computarse como la suma del costo de cada arco en el camino. Este camino no es necesariamente un camino óptimo entre s y n (puede haber caminos mejores no cubiertos aún por la búsqueda), pero su costo puede servir como un estimador g(n) del costo mı́nimo de 8.4 Búsqueda primero el mejor 93 ir de s a n. El otro término, h(n), es más problemático, porque el espacio entre n y t no ha sido explorado aún, por lo que su valor es una verdadera adivinanza heurı́stica, resuelta con base en el conocimiento general del algoritmo sobre la estructura particular del problema a resolver. Como h depende del dominio del problema, no existe un método universal para su construcción. Asumamos por el momento que una función h nos es dada y concentrémonos en los detalles del programa primero el mejor. 7 e 2 s 5 2 5 f 2 2 4 4 a b 2 c g 4 2 2 3 t 3 3 d Figura 8.6 Mapa entre ciudades, sus distancias por carretera, y sus distancias lı́neales a la meta (cuadros). Como ejemplo consideren el siguiente problema. Dado un mapa (Fig. 8.6), la tarea es encontrar la ruta más corta entre una ciudad inicial s y una ciudad meta t. Al estimar el costo del resto del camino de la ciudad X a la meta, usamos simplemente la distancia lineal denotada por dist(X,t). Entonces: f (X) = g(X) + h(X) = g(X) + dist(X,t) En este ejemplo, podemos imaginar la búsqueda de primero el mejor consistente de dos procesos, cada uno de ellos explorando uno de los dos caminos alternativos: el proceso 1 para el camino vı́a a y el proceso 2 para el camino vı́a e. En los pasos iniciales el proceso 1 está más activo porque los valores f en ese camino son más bajos que los del otro. En el momento en que el proceso 1 llega a c y el proceso 2 sigue en e, la situación cambia: f (c) = g(c) + h(c) = 6 + 4 = 10 f (e) = g(e) + h(e) = 2 + 7 = 9 94 8 Estrategias básicas de resolución de problemas De forma que f (e) < f (c) y ahora el proceso 2 procede al nodo f y el proceso 1 espera. Pero entonces: f ( f ) = 7 + 4 + 11 f (c) = 10 f (c) < f ( f ) por lo que el proceso 2 es detenido y se le permite al proceso 1 continuar, pero sólo hasta el nodo d ya que f (d) = 12 > 11. El proceso 2 continua corriéndo hasta llegar a la meta t (Fig. 8.7). s f(a)=2+5=7 e a f(e)=2+7=9 f(b)=4+4=8 b f(c)=6+4=10 f f(f)=7+4=11 c f(g)=9+2=11 g d f(t)=11+0=11 t Figura 8.7 La búsqueda en el mapa de las ciudades. Este proceso de búsqueda parte del nodo inicial (la ciudad s) y genera nuevos nodos sucesores, expandiendose siempre en la dirección más promisora de acuerdo a los valores de la función f . Esto genera un árbol que crece hasta llegar a un nodo meta. Este árbol será representado en nuestro programa de búsqueda primero el mejor por términos de dos formas: 1. l(N, F/G) representa una hoja del árbol, donde N es un nodo en el espacio de estados, G es g(N) y F es f (N) = G + h(N). 2. t(N, F/G, Subs) representa un nodo interno del árbol, con una lista de subárboles Subs no vacı́os. F es el valor f actualizado de N. El valor para f del sucesor más prometedor de N. La lista Subs se ordena de acuerdo al valor creciente de los valores para f de los subárboles. 8.4 Búsqueda primero el mejor 95 Por ejemplo, consideren nuevamente la búsqueda ilustrada en la figura 8.7. En el momento en que el nodo s es expandido, el árbol de búsqueda consta de tres nodos: el nodo s y sus hijos a y e. En nuestro programa, este árbol se representa como: t(s,7/0,[l(a,7/2),l(e,9/2)] El valor f para s es 7, esto es, el valor más promisorio de los hijos de s (a). El árbol crece expandiendo el nodo más primisorio. El más cercano competidor de a es e con un f valor de 9. Se permite que a crezca mientras su f valor no exceda 9. Por lo tanto los nodos b y c son generados, pero c tiene un f valor de 10, lo cual excede el umbral de crecimiento fijado en 9. En ese momento no se permite que a crezca más. En ese momento el árbol es: t(s,9/0,[l(e,9/2),t(a,10/2,[t(b,10/4,[l(c,10/6)])])]) Observen que ahora el valor f del nodo a es 10, mientras que el del nodo e es 9. Estos valores se actualizaron porque fueron generados los nodos b y c. Ahora el nodo sucesor más promisorio de s es s es e con un valor f de 9. La actualización de los f valores es necesaria para permitir al programa reconocer el subárbol más promisorio en cada nivel del árbol de búsqueda (esto es, el subárbol que contiene la hoja más promisoria). Esta modificación de los estimados de f , nos lleva a la generalización de la definición de f que extiende su definción de nodos a árboles. Para una hoja n del árbol, mantenemos la definición original: f (n) = g(n) + h(n) Para un subárbol T , cuya raı́z es n y tiene como subárboles S1 , S2 , . . . : f (T ) = mı́n f (Si ) i El programa que implementa la búsqueda primero el mejor es como sigue. Primero definimos una función interfaz, que encuentre la solución Sol a partir de un estado inicial Inicio. Para ello solucion/2 llama a expandir/6: 1 2 solucion(Inicio,Sol) :expandir([],l(Inicio,0/0),9999,_,si,Sol). El predicado expandir/6 se encarga de hacer crecer el árbol de búsqueda. Sus argumentos incluyen: El Camino recorrido, inicialmente vacı́o; El Arbol actual de búsqueda, inicialmente una hoja con el nodo Inicio y valor de 0 para F y G; El Umbral o limite para la expansión del árbol ( f -valor máximo), para este ejemplo 9999 es suficiente (ningún costo en el árbol será mayor que este valor); 96 8 Estrategias básicas de resolución de problemas El Arbol1 expandido bajo el Umbral (en consecuencia el f -valor de este árbol es mayor, al menos que se halla encontrado la solución). Originalmente se pasa una variable anónima en la llamada; La bandera Solucionado que puede tomar los valores si, no, o nunca; y la solución, si existe, al problema regresado en la variable Sol. El crecimiento del árbol se programa por casos. El caso más simple corresponde a aquel donde árbol de búsqueda es una hoja, y su Nodo es una meta del espacio de estados. En ese caso [Nodo|Camino] es la solución Sol buscada. Observen la bandera Solucionado = si. 1 2 expandir(Camino,l(Nodo,_),_,_,si,[Nodo|Camino]) :meta(Nodo). El segundo caso corresponde a un árbol de búsqueda que es una hoja, cuyo Nodo no es una meta del espacio de estados y tiene un f -valor menor (o igual) que el Umbral. Para ello se generan los árboles sucesores del árbol de búsqueda actual (Arboles) usando el predicado listaSucc/3. El árbol debe expandir/6 o fallar con Solucionado = nunca. 1 2 3 4 5 6 7 8 9 10 expandir(Camino,l(Nodo,F/G),Umbral,Arbol1,Solucionado,Sol) :F =< Umbral, (bagof(M/C, (s(Nodo,M,C), (not(member(M,P)))), Succ), !, listaSucc(G,Succ,Arboles), mejorF(Arboles,F1), expandir(Camino,t(N,F1/G,Arboles), Umbral,Arbol1,Solucionado,Sol) ; Solucionado=nunca). El tercer caso es parecido, pero el Nodo es interno. 1 2 3 4 5 6 7 8 9 expandir(Camino,t(Nodo,F/G,[Arbol|Arboles]), Umbral,Arbol1,Solucionado,Sol) :F =< Umbral, mejorF(Arboles,MejorF), min(Umbral,MejorF,Umbral1), expandir([Nodo|Camino],Arbol, Umbral1,Arbol1,Solucionado1,Sol), continuar(Camino,t(Nodo,F/G,[Arbol1|Arboles]), Umbral,Arbol1,Solucionado1,Solucionado,Sol). El caso cuatro cubre los puntos muertos, cuando no hay solución al problema: 1 expandir(_,t(_,_,[]),_,_,nunca,_) :- !. El caso cinco define la situación cuando el f -valor es mayor que el Umbral y se inhibe el crecimiento del árbol: 8.4 Búsqueda primero el mejor 1 2 97 expandir(_,Arbol,Umbral,Arbol,no,_):f(Arbol,F),F>Umbral. continuar/7 decide como procede la búsqueda de acuerdo al árbol expandido. Si una solución Sol se ha encontrado, se regresa este valor. En cualquier otro caso, la expansión continua dependiendo del valor de Solucionado (no o nunca). 1 continuar(_,_,_,_,si,si,Sol). 2 3 4 5 6 7 8 continuar(Camino,t(Nodo,F/G,[Arbol1|Arboles]), Umbral,Arbol1,no,Solucionado,Sol) :insert(Arbol1,Arboles,NodoArboles), mejorF(NodoArboles,MejorF), expandir(Camino,t(Nodo,F/G,NodoArboles), Umbral,Arbol1,Solucionado,Sol). 9 10 11 12 13 14 continuar(Camino,t(N,F/G,[_|Arboles]), Umbral,Arbol1,nunca,Solucionado,Sol) :mejorF(Arboles,MejorF), expandir(Camino,t(N,MejorF/G,Arboles), Umbral,Arbol1,Solucionado,Sol). Las siguientes funciones son auxiliares: 1 listaSucc(_,[],[]). 2 3 4 5 6 7 8 listaSucc(G0, [N/C|NCs], Arboles) :G is G0+C, h(N,H), F is G+H, listaSucc(G0,NCs,Arboles1), inserta(l(N,F/G,Arboles1),Arboles). 9 10 11 12 inserta(Arbol,Arboles,[Arbol|Arboles]) :f(Arbol,F), mejorF(Arboles,F1), F =< F1, !. 13 14 15 inserta(Arbol,[Arbol1|Arboles], [Arbol1|Arboles1]) :inserta(Arbol,Arboles,Arboles1). 16 17 f(l(_,F/_),F). 18 19 f(t(_,F/_,_),F). 20 21 22 mejorF([Arbol|_],F) :f(Arbol,F). 23 24 mejorF([],9999). 25 26 27 min(X,Y,X) :X =< Y, !. 98 28 29 min(_,Y,Y). 8 Estrategias básicas de resolución de problemas Capı́tulo 9 Sistemas Expertos Resumen En este capı́tulo abordaremos uno de los productos tı́picos de la Inteligencia Artificial: los Sistemas Expertos. Normalmente, usamos herramientas de desarrollo conocidas con shells para construir este tipo de sistemas, pero si necesitamos configurar un shell para una aplicación en particular, es necesario conocer como es que un sistema experto se construye desde cero. El capı́tulo constituye el segundo ejemplo del uso de Prolog para resolver problemas tı́picos de la Inteligencia Artificial 9.1. Introducción Los sistemas expertos (SE) son aplicaciones de cómputo que involucran experiencia no algorı́tmica, para resolver cierto tipo de problema. Por ejemplo, los sistemas expertos se usan para el diagnóstico al servicio de humanos y máquinas. Existen SE que juegan ajedrez, que planean decisiones financieras, que configuran computadoras, que supervisan sistemas de tiempo real, que deciden polı́ticas de seguros, y llevan a cabo demás tareas que requieren de experiencia humana. Los SE incluyen componentes del sistema en sı́ e interfaces con individuos con varios roles. Esto se ilustra en la figura 9.1. Los componentes más importantes son: Base de conocimientos. La representación declarativa de la experiencia, muchas veces en forma de reglas IF-THEN. Almacén de trabajo. Los datos especı́ficos al problema que se está resolviendo. Máquina de inferencia. El código central del SE que deriva recomendaciones con base en la base de conocimientos y los datos especı́ficos del problema. Interfaz del usuario. El código que controla el diálogo entre el usuario y el SE. Para entender un SE es necesario entender también el rol de los usuarios que interaccionan con el sistema: Experto del Dominio. El o los individuos que son los expertos en resolver el problema que el SE intentará resolver. 99 100 9 Sistemas Expertos Usuario Experto en el Dominio Interface con el Usuario Experiencia Ingeniero del Conocimieno Máquina de Inferencia Base de Conocimiento Ingeniero en Sistemas Almacén de Trabajo Figura 9.1 Componentes de un sistema experto e interfases humanas Ingeniero de Conocimiento. El individuo que codifica el conocimiento de los expertos en forma declarativa, para que pueda ser usado por el SE. Usuario. El individuo que consultará el SE para obtener los consejos que esperarı́a de un experto del dominio. Muchos SE se producen en ambientes de desarrollo conocidos como shells. Un shell es un sistema que contiene la interfaz del usuario, un formato de conocimiento declarativo para la base de conocimientos y una máquina de inferencia. El ingeniero de conocimiento usa el shell para construir un SE que resuelve problemas en un dominio particular. Si el sistema se construye desde cero, o utilizando shells configurados para cierto tipo de aplicaciones, otro individuo entra en escena: Ingeniero de Sistemas. La persona que construye la interfaz del usuario, diseña el formato declarativo de la base de conocimientos, e implementa la máquina de inferencia ¿adivinan cual es su rol? En realidad eso depende de la talla del proyecto: El ingeniero de conocimiento y el ingeniero del sistema, pueden ser la misma persona. El diseño del formato de la base de conocimientos y su codificación están ı́ntimamente relacionados. Al proceso de codificar el conocimiento de los expertos, se le conoce como ingenierı́a del conocimiento. Siendo ésta una tarea complicada, se espera el uso de los shells haga posible la reutilización del conocimiento codificado. En estas sesiones nos concentraremos en la programación en Prolog de los SE al margen del uso de las shells. 9.2 Caracterı́sticas de los SE 9.2. 101 Caracterı́sticas de los SE Los SE poseen las siguientes caracterı́sticas, en menor o mayor grado: Razonamiento guiado por las metas y encadenamiento hacia atrás. Una técnica de inferencia que usa las reglas IF-THEN para descomponer las metas en submetas más fáciles de probar. Manejo de incertidumbre. La habilidad del SE para trabajar con reglas y datos que no son conocidos con precisión. Razonamiento guiado por los datos y encadenamiento hacia adelante. Una técnica de inferencia que usa las reglas IF-THEN para deducir soluciones a un problema a partir de los datos iniciales disponibles. Representación de datos. La forma en que los datos especı́ficos a un problema dado, son almacenados y accesados por el SE. Interfaz del usuario. La parte del SE que se usa para una interacción más amigable con el usuario. Explicación. La habilidad del SE para explicar sus procesos de razonamiento y su uso en el cómputo de recomendaciones. 9.2.1. Razonamiento basado en metas El encadenamiento hacia adelante, o razonamiento basado en metas, es una forma eficiente de resolver problemas que pueden ser modelados como casos de “selección estructurada”; donde la meta del SE es elegir la mejor opción de entre varias posibilidades enumeradas. Por ejemplo, los problemas de identificación caen en esta categorı́a. Los problemas de diagnóstico tambien caben aquı́, pues se trata de elegir el diagnóstico adecuado. El conocimiento se codifica en reglas que describen como es que cada caso posible podrı́a ser seleccionado. La regla rompe el problema en sub-problemas. Por ejemplo, las siguientes reglas formarı́an parte de un SE para identificar aves: 1 IF 2 familia es albatros AND color es blanco THEN ave es albatros laysan. 3 4 5 6 7 IF 8 familia es albatros AND color es negro THEN ave es albatros de pies negros. 9 10 11 El sistema puede usar otras reglas para resolver las sub-metas planteadas por las reglas de alto nivel, por ejemplo: 102 9 Sistemas Expertos 1 IF 2 orden es tubonasales AND tamaño es grande AND alas es grandes anguladas THEN familia es albatros. 3 4 5 6 9.2.2. Incertidumbre Es muy común en la resolución de problemas de selección estructurada, que la respuesta final no es conocida con total certeza. Las reglas del experto pueden ser vagas, o el usuario puede estar inseguro sobre sus respuestas. Esto es fácilmente observable en el diagnóstico médico. Los SE normalmente usan valores numéricos para representar certidumbre. Existen diveras maneras de definirlos y usarlos en el proceso de razonamiento. 9.2.3. Razonamiento guiado por los datos Para muchos problemas no es posible enumerar las soluciones alternativas a las preguntas planteadas con antelación. Los problemas de configuración caen en esta categorı́a. El encadenamiento hacia adelante, o razonamiento guiado por los datos, usa reglas IF-THEN para explorar el estado actual en la solución del problema y moverse a estados más cercanos a la solución. Un SE para acomodar el mobiliario puede tener reglas para la ubicación de un mueble en particular. Una vez que un mueble ha sido colocado, se puede proceder con los demás. La regla para colocar la TV enfrente del sofá es como sigue: 1 IF 2 no_colocada tv AND sofá en pared(X) AND pared(Y) opuesta a pared(X) THEN colocar tv en pared(Y). 3 4 5 6 Esta regla toma un estado del problema con la televisión no situada y regresa un estado nuevo, donde la televisión ya ha sido colocada. Puesto que la televisión ya ha sido colocada en su lugar, esta regla no volverá a ser disparada por el SE. Otras reglas serán usadas para colocar el resto de los muebles hasta terminar. 9.3 Usando la máquina de inferencia de Prolog 9.3. 103 Usando la máquina de inferencia de Prolog Como pueden haber adivinado, Prolog posee una máquina de inferencia por encadenamiento hacı́a atrás. Esta máquina puede usarse parcialmente para implementar algunos SE. Las reglas de Prolog serán usadas para representar conocimiento y su máquina de inferencia será usada para derivar conclusiones. Otras partes del sistema, como la interfaz con el usuario deberán escribirse usando Prolog. Usaremos el problema de identificación de aves norteamericanas para ilustrar la construcción de un SE con Prolog. La experticia del SE se basa en un subconjunto de las reglas reportadas en Birds of North America de Robbins, Bruum, Zim y Singer. Las reglas del SE estarán diseñadas para ilustrar como se pueden representar varios tipos de conocimiento, en vez de buscar una identificación precisa de las aves. 9.3.1. Reglas Las reglas de un SE normalmente toman el siguiente formato: 1 IF 2 primera premisa AND segunda premisa AND ... THEN conclusión 3 4 5 6 La parte IF de la regla se conoce con el lado izquierdo de la regla (LHS), y la parte del THEN se conoce como el lado derecho de la regla (RHS). Esto es equivalente a la semantica de la regla Prolog: 1 2 3 4 conclusión :primera premisa, segunda premisa, ... Esto puede ser confuso pués la regla en prolog dice más THEN-IF que IF-THEN. Retomemos los ejemplos anteriores, si queremos representar en Prolog la regla: 1 IF 2 familia es albatros AND color es blanco THEN ave es albatros laysan 3 4 5 Tendrı́amos que escribir: 104 1 2 3 9 Sistemas Expertos ave(albatros_laysan) :familia(albatros), color(blanco). Las siguientes reglas distinguen entre dos tipos de albatros y cisne. Todas son cláusulas del predicado ave/1: 1 2 3 4 5 6 7 8 9 10 11 12 ave(albatros_laysan) :familia(albatros), color(blanco). ave(albatros_patas_negras) familia(albatros), color(obscuro). ave(cisne_silbador) :familia(cisne), voz(suave_musical). ave(cisne_trompetero) :famila(cisne), voz(alta_trompeta). :- Para que estas reglas tengan éxito al distinguir un ave, necesitamos almacenar hechos acerca del ave que deseamos identificar con el SE. Por ejemplo, si agregamos estos hechos al programa: 1 2 familia(albatros). color(obscuro). Ahora podemos usar la pregunta siguiente: 1 2 3 ?- ave(X). X = albatros_patas_negras Yes Observen que aún en esta etapa temprana tenemos un SE completo, donde la experticia consiste en distinguir entre cuatro aves. La interfaz con el usuario es el REPL de Prolog y los datos de entrada se almacenan directamente en el programa. 9.3.2. Reglas para relaciones jerárquicas El siguiente paso será representar la naturaleza jerárquica del sistema de clasificación de un ave. Esto incluirá reglas para identificar la familia y el orden del ave. Continuando con el albatros y el cisne, los predicados para orden/1 y f amilia/1 son: 1 2 orden(nariz_tubular) :fosas(externas_tubulares), 9.3 Usando la máquina de inferencia de Prolog 3 4 5 6 7 8 9 10 11 12 13 14 15 16 105 habitat(mar), pico(gancho). orden(acuatico) :patas(membrana), pico(plano). familia(albatros) :orden(nariz_tubular), tamaño(grande), alas(muy_largas). familia(cisne) :orden(acuatico), cuello(largo), color(blanco), vuelo(pesado). Ahora el SE puede identificar al albatros a partir de observaciones fundamentales sobre el ave. En la primer versión, f amilia/0 fue implementada como un hecho. Ahora f amilia/1 es implementada como una regla. Los hechos del SE ahora reflejan más datos primitivos: 1 2 3 4 5 6 fosas(externas_tubulares). habitat(mar). pico(gancho). tamaño(grande). alas(muy_largas). color(obscuro). La consulta siguiente reporta: 1 2 3 ?- ave(X). X = albatros_patas_negras Yes 9.3.3. Reglas para otras relaciones El ganso canadiense puede usarse para agregar complejidad al sistema. Debido a que esta ave pasa los veranos en Canadá y los inviernos en los Estados Unidos, su identificación se ve afectada por donde ha sido vista y en que estación. Dos reglas serán necesarias para cubrir estas situaciones: 1 2 3 4 5 6 ave(ganso_canadiense) :familia(ganso), estacion(invierno), pais(estados_unidos), cabeza(negra), pecho(blanco). 106 7 8 9 10 11 12 9 Sistemas Expertos ave(ganso_canadiense) :familia(ganso), estacion(verano), pais(canada), cabeza(negra), pecho(blanco). Estas metas pueden hacer referencia a otros predicados en una jerarquı́a diferente: 1 2 3 4 5 6 pais(estados_unidos) :- region(oeste_medio). pais(estados_unidos) :- region(sur_oeste). pais(estados_unidos) :- region(nor_oeste). pais(estados_unidos) :- region(atlantico_medio). pais(canada) :- provincia(ontario). pais(canada) :- provincia(quebec). 7 8 9 10 region(nueva_inglaterra) :estado(X), member(X,[massachusetts, vermont, connecticut, maine]). 11 12 13 14 region(sur_oeste) :estado(X), member(X,[florida, mississippi, alabama, nueva_orleans]). Otras aves necesitarán de predicados múltiples para ser identificada. Por ejemplo, el Mallard (Anas platyrhynchos), o pato común del norte, macho tiene la cabeza verde con un anillo blanco; la hembra tiene la cabeza café moteada: 1 2 3 4 5 6 7 8 ave(mallard):familia(pato), voz(graznido), cabeza(verde). ave(mallard) :familia(pato), voz(graznido), cabeza(cafe_moteada). Basicamente, cualquier situación del libro de las aves norte americanas puede ser expresado fácilmente en Prolog. Las reglas expresadas forman la base de conocimientos del SE. El único punto débil del programa es su interfaz con el usuario, que requiere que los datos sean introducidos como hechos del programa. 9.4 Interfaz del usuario 9.4. 107 Interfaz del usuario El sistema puede mejorarse considerablemente si proveemos una interfaz para el usuario, que pregunte por la información cuando esto sea necesario, en lugar de forzar al usuario a introducirla como hechos del programa. Antes de pensar en un predicado pregunta, es necesario entender la estructura de los datos que serán preguntados. Todos los datos, manejandos hasta ahora, han sido de la forma atributo–valor. Por ejemplo, los atributos del pato del norte Mallard, son mostrados en la figura 9.1. atributo familia voz cabeza valor pato graznido verde Cuadro 9.1 Atributos valor para el mallard Esta es una de las representaciones más simples usadas en los SE, pero es suficiente para muchas aplicaciones. Existen representaciones más expresivas, como los tripletes objeto–atributo–valor, o las redes semánticas, o los marcos. Como estamos programando en Prolog, la riqueza del lenguaje puede usarse directamente en el SE. Por ejemplo, los pares atributo–valor han sido representados como predicados unarios de la forma atributo(valor): familia(pato), voz(graznido), cabeza(verde). Pero en region/1 usamos la membresia en listas para su definición. Usaremos el predicado pregunta para determinar con ayuda del usuario, cuando un par atributo–valor es verdadero. El SE debe modificarse para determinar que atributos son verificables por el usuario. Esto se logra con reglas para los atributos que llaman a pregunta: 1 2 3 4 5 come(X) :- pregunta(come,X). pies(X) :- pregunta(pies,X). alas(X) :- pregunta(alas,X). cuello(X) :- pregunta(cuello,X). color(X) :- pregunta(color,X). Ahora, si el SE tiene como meta probar color(blanco), llamará a pregunta/2 en lugar de consultar su base de conocimientos. Si pregunta(color, blanco) tiene éxito, entonces color(blanco) también lo tiene. La versión más simple de pregunta es como sigue: 1 2 3 4 pregunta(Atrib,Val):write(Atrib:Val), write(’? ’), read(si). 108 9 Sistemas Expertos El predicado read/1 tendrá éxito sólo si el usuario responde “si” y falla si el usuario responde cualquier otra cosa. Ahora el programa puede ser ejecutado sin datos de trabajo iniciales. La misma llamada a ave/1 inicia la consulta al SE. 1 2 3 4 5 6 7 8 9 ?- ave(X). fosas_nasales : externas tubulares ? si. habitat : mar ? si. pico : ganchudo ? si. tamaño : grande ? si. alars : largas ? si. color : blanco ? si. X = albatros_laysan Yes. El problema con este enfoque es que si el usuario responde “no” a la última pregunta, la regla para ave(albratros laysan) falla, llevandonos a un backtracking. De esta manera el SE nos preguntarı́a nuevamente información que ya sabe. De alguna manera deberı́amos implementar un predicado pregunta que recuerde lo preguntado. Definiremos un nuevo predicado conocido/3 que nos ayude a recordar las respuestas del usuario. Las respuestas no se guardarán directamente en memoria, sino que serán guardadas dinámicamente con asserta/1 cuando pregunta provea información nueva para el SE: 1 pregunta(A,V) :- conocido(si,A,V), !. 2 3 pregunta(A,V) :- conocido(_,A,V), !, fail. 4 5 6 7 8 9 10 pregunta(A,V) :write(A:V), write’? : ’), read(Resp), asserta(conocido(Resp,A,V)), Resp == si. También es posible utilizar menues contextuados para el caso de atributos multivariados. La idea es que para atributos de un solo valor, la interfaz por el usuario pregunte una sola vez: 1 2 3 4 5 6 pregunta(A,V) :not(multivariado(A)), conocido(si,A,V2), V \== V2, !, fail. Una guı́a sobre los valores válidos para un atributo se implementa con el predicado menu pregunta que trabaja de manera análoga a pregunta: 9.5 Un Shell simple 1 2 3 4 109 tamaño(X) :menu_pregunta(tamaño, X, [grande, mediano, pequeño]). color(X) :menu_pregunta(color,X,[blanco,verde,cafe,negro]). La definición de menu pregunta/3 es: 1 2 3 4 5 6 7 8 menu_pregunta(A,V,MenuLista) :write(’Cual es el valor para ’, write(A), write(’? ’), nl, write(MenuLista),nl, read(Resp), checar(Resp,A,V,MenuLista), asserta(conocido(si,A,X)), X == V. 9 10 11 checar(X,A,V,MenuLista) :member(X,MenuLista), !. 12 13 14 15 checar(X,A,V,MenuLista) :write(’Ese valor no es válido, intente nuevamente’), nl, menu_pregunta(A,V,MenuLista). 9.5. Un Shell simple El ejemplo de identificación de aves tiene dos partes: una base de conocimientos, que incluye la información especı́fica sobre las aves; y los predicados para controlar la interfaz con el usuario. Al separar estas dos partes, podemos crear un shell de SE. Con ello podemos crear un nuevo SE que identifique, por ejemplo, peces y reutilizar la parte de control de la interfaz. Un cambio mı́nimo es necesario para separar las dos partes de nuestro SE. Necesitamos un predicado de alto nivel que inicie el proceso de identificación. Puesto que no sabemos de antemano lo que el SE va a identificar, el shell buscará satisfacer un predicado llamado meta. Cada base de conocimiento deberá tener definido meta/1, por ejemplo, para el caso de identificación de aves tendrı́amos: 1 meta(X) :- ave(X). como primer predicado en la base de conocimientos aves. El shell tendrá un predicado solucion/0 que llevará a cabo labores de mantenimiento del SE, para luego resolver la meta/1: 1 2 solucion :abolish(conocido,3), 110 3 4 5 9 Sistemas Expertos define(conocido,3), meta(X), write(’La respuesta es: ’), write(X), nl. 6 7 8 solucion :write(’No se encontró una respuesta.’), nl. El predicado Prolog abolish/2 se usa para eliminar los hechos definidos previamente con conocido/3, cada vez que una consulta se va a ejecutar. Esto permite al usuario ejecutar solucion multiples veces en una sola sesión. El predicado de f ine/2 permite indicarle a Prolog que conocido estará definido en el SE, de forma que no cause error la primera utilización de este predicado. Este predicado puede variar dependiendo de la versión de Prolog utilizada. De esta manera tenemos que el SE ha sido dividido en dos partes. Los predicados en el shell son: solucion, pregunta, menu pregunta, los predicados auxiliares de éstos. Los predicados en la base de conocimientos son: meta, las reglas sobre el conocimiento del SE, las reglas sobre los atributos provistos por el usuario, las declaraciones de los atributos multi-variados. Para usar este shell en Prolog, tanto el shell como la base de conocimientos deben ser cargados: 1 2 3 4 5 6 ?- consult(shell). yes ?- consult(’aves.kb’). yes ?- solucion. fosas_nasales : externas_tubulares ? ... 9.5.1. REPL El shell puede ser mejorado construyendo un ciclo de comandos read-eval-print loop. Para ello definiremos el predicado se: 1 2 se :bienvenida, 9.6 Encadenamiento hacı́a atrás con incertidumbre 3 4 5 6 7 111 repeat, write(’> ’), read(X), do(X), X == quit. 8 9 10 11 bienvenida :write(’Este es el shell de su SE.’), nl, write(’Escriba: cargar, consultar, o salir en el promt.’), nl 12 13 14 do(cargar) :cargar_bd, !. 15 16 17 do(consultar) :solucion, !. 18 19 do(salir). 20 21 22 23 24 do(X) :write(X), write(’ no es un comando válido.’), nl, fail. 25 26 27 28 29 cargar_bd :write(’Nombre del archivo: ’), read(F), reconsult(F). La arquitectura obtenida de esta forma se muestra en la figura 9.2. 9.6. Encadenamiento hacı́a atrás con incertidumbre Como hemos mencionado, el encadenamiento hacı́a adelante resulta conveniente cuando los problemas a resolver son del tipo selección estructurada, como en el ejemplo de la clasificación de aves. Sin embargo, en además de que hemos asumido que la información completa está disponible para resolver el problema, también hemos asumido que no hay incertidumbre, ni el los datos provistos por el usuario, ni en las reglas de los expertos. Por ejemplo, el albatros puede ser observado en la bruma, con lo que serı́a difı́cil precisar si su color es blanco u obscuro. Es de esperar que un SE que maneje incertidumbre, pueda contender con este tipo de problemas. Desarrollaremos un shell que permita manejar reglas con incertidumbre y encadenamiento de ellas hacı́a atrás. Evidentemente, este SE tendrá un formato de reglas propio, diferente a las reglas de Prolog, y por lo tanto, una máquina de inferencia propia. 112 9 Sistemas Expertos Interfaz del Usuario se pregunta menu_pregunta Máquina de inferencia solucion cargar Base de Conocimientos Memoria de trabajo meta reglas mulivaluado preguntado conocido Figura 9.2 El shell del SE. 9.6.1. Factores de certidumbre La forma más común de trabajar con la incertidumbre consiste en asignar un factor de certidumbre a cada pieza de información en el SE. La máquina de inferencia deberá mantener los factores de incertidumbre conforme el proceso de inferencia se lleve a cabo. Por ejemplo, asumamos que los factores de certidumbre (precedidos por cf) son enteros entre -100 (definitivamente falso) y +100 (definitivamente verdadero). La siguiente base de conocimientos en formato del SE está diseñada para diagnosticar un auto que no enciende. Esto ilustra el comportamiento de los factores de certidumbre: 1 GOAL problema. 2 3 4 5 6 RULE 1 IF not arranca AND bateria_mala THEN problema is bateria. 7 8 9 10 RULE 2 IF luces_debiles THEN bateria_mala cf 50. 11 12 13 RULE 3 IF radio_debil 9.6 Encadenamiento hacı́a atrás con incertidumbre 14 113 THEN bateria_mala cf 50. 15 16 17 18 19 RULE 4 IF arranca AND olor_gasolina THEN problema is fuga cf 80. 20 21 22 23 24 RULE 5 IF arranca AND indicador_gasolina is vacio THEN problema is tanque_vacio cf 90. 25 26 27 28 29 RULE 6 IF arranca AND indicador_gasolina is bajo THEN problema is tanque_vacio cf 30. 30 31 32 33 ASK arranca MENU (si no) PROMPT ’Su motor arranca? ’. 34 35 36 37 ASK luces_debiles MENU (si no) PROMPT ’Sus luces están débiles? ’. 38 39 40 41 ASK radio_debile MENU (si no) PROMPT ’Su radio está débil? ’. 42 43 44 45 ASK olor_gasolina MENU (si no) PROMPT ’Huele a gasolina?’. 46 47 48 49 ASK indicador_gasolina MENU (vacio, medio, lleno) PROMPT ’Que indica al aguja de gasolina? ’. Por el momento la inferencia usarı́a encadenamiento hacı́a atrás, similar al que usa Prolog. La regla GOAL indica que el proceso buscará un valor para problema. La regla 1 causará que la sub-meta bateria mala sea procesada, etc. Observen que las reglas especifican también factores de certidumbre. Las reglas 2 y 3 proveen evidencia de que la baterı́a está en mal estado, pero ninguna es conclusiva al respecto. Un diálogo con este sistema serı́a como sigue: 1 2 3 4 5 6 7 8 consultar, reiniciar, cargar, listar, trazar, cómo, salida : consultar Su motor arranca? : si Huele a gasolina? : si Qué indica la aguja de la gasolina? : vacio 114 9 10 11 9 Sistemas Expertos problema-tanque-vacio-cf-90 problema-fuga-cf-80 problema resuelto Observen que a diferencia de Prolog, el sistema no se detiene al encontrar el primer posible valor para problema. En este caso se computan todos los valores razonables para problema y se reporta el valor de certidumbre asociado a estas soluciones. Recordemos que estos factores de certidumbre no son probabilidades, solo ponderan de alguna manera las respuestas. De igual manera, el usuario podrı́a ofrecer factores de certidumbre sobre sus respuestas, por ejemplo: 1 2 3 4 5 : consultar ... Huele a gasolina? si cf 50 ... Existen diversas maneras de capturar el concepto de factor de certidumbre, pero todas ellas deben de confrontar las mismas situaciones básicas: Reglas cuyas conclusiones son inciertas, Reglas cuyas premisas son inciertas, Datos provistos por el usuario inciertos, Combinación de premisas inciertas con conclusiones inciertas, Actualizar los factores de incertidumbre en los datos almacenados en el espacio de trabajo, Establecer un umbral sobre el cual las premisas se consideran conocidas. 9.6.2. Factores de certidumbre à la MYCIN MYCIN, uno de los SE más conocidos en IA, introduce factores de certidumbre diseñados para producir resultados intuitivos desde la perspectiva de los expertos. Revisemos el uso de estos factores por casos. El más simple, serı́a aquel donde las premisas son totalmente ciertas: 1 2 arranca cf 100. olor_gas cf 100. disparan la regla 4 y por tanto, problema fuga cf 80 deberá agregarse al almacén de trabajo. Sin embargo, este es un caso poco probable. Normalmente no estamos totalmente seguros de las premisas de una regla y lo normal serı́a tener hechos como: 9.6 Encadenamiento hacı́a atrás con incertidumbre 1 2 115 arranca cf 80. olor_gas cf 50. Cuando esto sucede, la incertidumbre en las premisas de la regla debe combinarse con las de la conclusión de la misma de la siguiente manera: CF = CFregla × mı́nCF premisa/100 Dado el ejemplo, la regla 4 se activarı́a con un c f = 50 (el mı́nimo de las dos premisas) y dada la fórmula anterior, agregarı́amos problema fuga cf 40 al almacén de trabajo. Para que una regla dispare, su factor de certidumbre debe superar un umbral que normalmente se fija en 20. Ası́ que bajo la definición anterior, la regla 4 dispararı́a. Si tuviésemos olor gas cf 15, entonces la regla no dispararı́a. Ahora consideren el caso donde hay más de una regla que da soporte a cierta conclusión. En ese caso, cada una de las reglas que disparan contribuirá al factor de certidumbre de la conclusión. Si una regla dispara y la conclusión ya se encontraba en el almacén de trabajo, las siguientes reglas aplican: CF(X,Y ) = X +Y (100 − X)/100. Ambos X,Y > 0 CF(X,Y ) = X +Y /1 − mı́n(|X|, |Y |). Uno de X,Y < 0 CF(X,Y ) = −CF(−X, −Y ). Ambos X,Y < 0 Por ejemplo, si disparamos la regla 2 (luces débiles) con su premisa sin incertidumbre, tendrı́amos que agregar al almacén de trabajo bateria mala cf 50. Luego si disparamos la regla 3 (radio débil), el factor de certidumbre de este hecho debe modificarse a bateria mala cf 75. Lo cual resulta intuitivo (hay más evidencia de que la baterı́a tiene problemas). Lo que también resulta intuitivo es que necesitamos programar nuestra propia máquina de inferencia. 9.6.3. Formato de las reglas Como programaremos nuestra propia máquina de inferencia, podemos elegir la estructura de hechos y reglas. Las reglas tendrán la estructura general: regla(Nombre, Premisas,Conclusion). El Nombre opera solo como un identificador de la regla. El lado izquierdo de la misma Premisas implica al lado derecho Conclusion (conclusión). Como usaremos encadenamiento hacı́a atrás, cada regla será usada para validar una pieza de información, de manera el RHS contiene una meta con su factor de certidumbre asociado: 116 9 Sistemas Expertos conclusion(Meta,CF). mientras que las premisas toman la forma de una lista de metas: premisas(ListaMetas). Las metas serán representadas, para comenzar, como pares atributo–valor: av(Atributo,Valor). cuando Atributo y Valor son átomos, la estructura general de las reglas se ve como: 1 2 3 regla(Nombre, premisas( [av(A1,V1), av(A2,V2), ... ] ), conclusion(av(Attr,Val), CF)). Por ejemplo, la regla 5 quedarı́a representada como: 1 2 3 regla(5, premisas([av(arranca,si), av(indicador_gasolina,vacio)]), conclusion(av(problema,fuga), 80)). Estas reglas no son fáciles de leer, pero tienen una estructura adecuada para ser procesadas por Prolog. Otras herramientas de Prolog como las gramáticas de cláusula definitivas (DCG) o la definición de operadores, puede ayudarnos a simplificar esta representación. 9.6.4. La máquina de inferencia Dado el formato de las reglas del SE deseamos que la inferencia tome en cuenta los siguientes aspectos: Combine los factores de certidumbre como se indico anteriormente. Mantenga el espacio de trabajo con la información actualizada con las nuevas evidencias obtenidas. Encontrar toda la información acerca de un atributo en particular cuando se pregunte por él, y poner esa información en el espacio de trabajo. Primero, los hechos serán almacenados en la memoria de trabajo de Prolog, con el siguiente formato: 1 hecho(av(A,V),CF). 9.6 Encadenamiento hacı́a atrás con incertidumbre 117 De forma que un predicado meta/2 harı́a la llamada para resolver un problema dado en estos términos. Por ejemplo, en el caso del arranque del auto, tendrı́amos como meta: 1 ?- meta(av(problema,X),CF). El predicado meta/2 debe de contender con tres casos: El atributo–valor se conoce de antemano; Existen reglas para deducir el atributo–valor; Se debe preguntar al usuario. El sistema puede diseñarse para preguntar al usuario automáticamente por el valor de un atributo, ante la ausencia de reglas; o bien, se puede declarar que atributos pueden ser preguntados al usuario. Este último enfoque hace que el manejo de la base de conocimientos sea más explı́cito y provee mayor control sobre los diálogos usuario – SE. Podemos definir un predicado pregunta/2 para declarar el atributo a preguntar y la frase para ello: 1 pregunta(pais_residencia,’¿En qué paı́s vive? ’). Veamos ahora los tres casos para meta/2. El primero de ellos ocurre cuando la información ya está en la memoria de trabajo: 1 2 3 meta(av(Atr,Val),CF) :hecho( av(Atr,Val), CF), !. El segundo caso se da cuando el valor del atributo no se encuentra en la memoria de trabajo, pero el es posible preguntar por ello al usuario: 1 2 3 4 5 6 meta(av(Atr,Val), CF) :\+ hecho( av(Atr,_),_), pregunta(Atr,Msg), preguntar(Atr,Msg), !, meta(av(Atr,Val), CF). Para ello, el predicado preguntar/2 interroga al usuario. El usuario responde con un valor para la atributo Atr y un factor de certidumbre asociado CF. El mensaje Msg da la información necesaria para guiar al usuario en estas consultas: 1 2 3 4 5 preguntar(Atr,Msg) :write(Msg), read(Val), read(CF), asserta(fact(av(Atr,Val),CF)). 118 9 Sistemas Expertos El tercer caso para meta/2 es cuando el valor del atributo es desconocido, pero se puede deducir usando las reglas definidas en el sistema, en ese caso la llamada es: 1 2 meta(Meta,CFactual) :buscaReglas(Meta,CFactual). Esta llamada hace uso de la máquina de inferencia que diseñaremos para nuestro SE con incertidumbre. El factor de certidumbre se etiqueta como actual, porque es posible que cambie de valor al ir aplicando las reglas definidas en el sistema. El predicado buscaReglas/2 se encarga de encontrar aquellas reglas cuya conclusión unifica con la Meta en cuestión y de actualizar el factor de certidumbre con base en las premisas de estas reglas. Si la Meta es un hecho conocido, no hay nada que hacer, sólo regresar true: 1 2 3 4 5 6 7 8 buscaReglas(Meta,CFactual) :regla(N, premisas(ListaPremisas), conclusion(Meta,CF)), probar(ListaPremisas,Contador), ajustar(CF,Contador,NuevoCF), actualizar(Meta,NuevoCF,CFactual), CFactual == 100, !. 9 10 11 buscaReglas(Meta,CF) :hecho(Meta,CF). Dada una lista de premisas pertenecientes a una regla encontrada para satisfacer la Meta del SE, es necesario que buscaReglas/2 las pruebe. Para ello definimos probar/2: 1 2 probar(ListaPremisas, Contador) :probAux(ListaPremisas, 100, Contador). 3 4 5 6 7 8 probAux([],Contador,Contador). probAux([Premisa1|RestoPremisas],ContadorActual,Contador) :meta(Premisa1,CF,Cont), Cont >= 20, probAux(RestoPremisas,Cont,Contador). El ajuste de los factores de certidumbre se lleva a cabo de la siguiente manera: 1 2 3 ajustar(CF1, CF2, CF) :X is CF1 * CF2 / 100, int_redondear(X,CF). 4 5 6 7 int_redondear(X,I) :X >= 0, I is integer(X + 0.5). 9.6 Encadenamiento hacı́a atrás con incertidumbre 119 8 9 10 11 int_redondear(X,I) :X < 0, I is integer(X - 0.5). La actualización de la memoria de trabajo se lleva a cabo de la siguiente manera: 1 2 3 4 5 6 actualizar(Meta,NuevoCF,CF) :hecho(Meta,ViejoCF), combinar(NuevoCF,ViejoCF,CF), retract(hecho(Meta,ViejoCF)), asserta(hecho(Meta,CF)), !. 7 8 9 actualizar(Meta,CF,CF) :asserta(hecho(Meta,CF)). 10 11 12 13 14 15 combinar(CF1, CF2, CF) :CF1 >= 0, CF2 >= 0, X is CF1 + CF2*(100 - CF1)/100, int_redondear(X,CF). 16 17 18 19 20 21 22 combinar(CF1,CF2,CF) :CF1 < 0, CF2 < 0, X is -( -CF1-CF2*(100+CF1)/100), int_redondear(X,CF). 23 24 25 26 27 28 29 combinar(CF1,CF2,CF) :(CF1 < 0 ; CF2 < 0), (CF1 > 0 ; CF2 > 0), abs_minimum(CF1,CF2,MCF), X is 100 * (CF1 + CF2) / (100 - MCF), int_redondear(X,CF). 9.6.5. Interfaz con el usuario La interfaz con el usuario es muy parecida a la definida en la sección anterior. Se incluyen predicados auxiliares necesarios en su definición: 1 2 3 4 5 6 se :repeat, write(’consultar, cargar, salir’), nl, write(’: ’), read_line(X), ejec(X), 120 7 9 Sistemas Expertos X == salir. 8 9 10 11 ejec(consultar) :metas_principales, !. 12 13 14 15 ejec(cargar) :cargar_reglas, !. 16 17 ejec(salir). 18 19 %% % Auxiliares 20 21 22 23 24 25 26 metas_principales :meta_principal(Atr), principal(Atr), imprime_meta(Atr), fail. metas_principales. 27 28 29 30 31 principal(Atr) :meta(av(Atr,Val,CF)), !. principal(_) :- true. 32 33 34 35 36 37 38 39 40 41 imprime_meta(Atr) :nl, hecho(av(Atr,Val), CF), CF >= 20, salidap(av(Atr,Val),CF), nl fail. imprime_meta(Atr) :write (’Meta: ’), write(Attr), write(’ solucionada.’), nl, nl. 42 43 44 45 46 47 48 salidap(av(Atr,Val),CF) :output(Atr,Val,ListaImprimir), write(Atr-’cf’-CF), imprimeLista(ListaImprimir), !. salidap(av(Atr,Val),CF) :write(Atr-Val-’cf’-CF). 49 50 51 52 53 imprimeLista([]). imprimeLista([X|Xs]) :write(X), imprimeLista(Xs). Capı́tulo 10 Arboles de Decisión Resumen En este capı́tulo abordaremos la solución de problemas en el contexto del aprendizaje automático, ejemplificado con el algoritmo ID3 [13] (Inductive Dicotomizer). Este algoritmo induce árboles de decisión a partir de ejemplos conformados como un conjunto de pares atributo–valor, para predecir el valor de uno de los atributos, conocido como la clase. El aprendizaje de árboles de decisión es una de las técnicas de inferencia inductiva más usadas. Se trata de un método para aproximar funciones de valores discretos, capaz de expresar hipótesis disyuntivas y robusto al ruido en los ejemplos de entrenamiento. La descripción que se presenta en este capı́tulo, cubre una familia de algoritmos para la inducción de árboles de decisión que incluyen ID3 y C4.5 [14]. Estos algoritmos llevan a cabo su búsqueda de hipótesis en un espacio completamente expresivo, evitando ası́ los problemas mencionados con respecto a espacios de hipótesis incompletos. Como veremso, el sesgo inductivo en este caso, consiste en la preferencia por árboles pequeños, sobre árboles grandes. Un árbol ası́ aprendido, puede representarse también como un conjunto de reglas si-entonces, más fáciles de entender para un usuario. 10.1. Representación de los árboles de decisión La figura 10.1 muestra un árbol de decisión tı́pico. Cada nodo del árbol está conformado por un atributo y puede verse como la pregunta: ¿Qué valor tiene este atributo en el caso a clasificar? Las ramas que salen de los nodos, corresponden a los posibles valores del atributo correspondiente. Un árbol de decisión clasifica a un caso, filtrandolo de manera descendente, hasta encontrar una hoja, que corresponde a la clasificación buscada. Consideren el proceso de clasificación del siguiente caso, que describe un dı́a en partı́cular: h cielo = soleado,temperatura = caliente, humedad = alta, viento = f uerte i 121 122 10 Arboles de Decisión Atributo Cielo Clase Valor nublado lluvioso soleado Húmedad alta no si Viento fuerte normal si no débil si Figura 10.1 Un ejemplo de arbol de decisión para el concepto “buen dı́a para jugar tenis”. Los nodos representan un atributo a ser verificado por el clasificador. Las ramas son los posibles valores para el atributo en cuestión. Los textos en cı́rculos, representan las clases consideradas, i.e., los valores posibles del atributo objetivo. Como el atributo Cielo, tiene el valor soleado en el caso, éste es filtrado hacı́a abajo del árbol por la rama de la izquierda. Como el atributo Humedad, tiene el valor alta, el ejemplo es filtrado nuevamente por rama de la izquierda, lo cual nos lleva a la hoja que indica la clasificación del caso: Buen dı́a para jugar tenis = no. El Algoritmo 2, define computacionalmente esta idea. Algoritmo 2 El algoritmo clasifica, para árboles de decisión 1: function C LASIFICA(Ej, Arbol) Require: E j: un ejemplo a clasificar, Arbol: un árbol de decisión Ensure: Clase: la clase del ejemplo 2: Clase ← tomaValor(raiz(Arbol), E j); 3: if hoja(raı́z(Arbol)) then 4: return Clase 5: else 6: clasi f ica(E j, subArbol(Arbol,Clase)); 7: end if 8: end function La función toma-valor encuentra el valor de un atributo, en el caso que se está clasificando. El predicado hoja es verdadero si su argumento es un nodo terminal del árbol y falso si se trata de un nodo interno. La función sub-árbol se mueve por la rama del árbol que corresponde al valor del atributo probado en el caso. De esta forma, obtiene un sub-árbol. En nuestro ejemplo, a partir del nodo raı́z cielo, esta función obtiene el sub-árbol que resulta de moverse por la rama soleado, etc. En general, un árbol de decisión representa una disyunción de conjunciones de restricciones en los posibles valores de los atributos de los casos. Cada rama que va 10.2 Problemas apropiados para la aplicación de árboles de decisión 123 de la raı́z del árbol a una hoja, representa una conjunción de tales restricciones y el árbol mismo representa la disyunción de esas conjunciones. Por ejemplo, el árbol de la figura 10.1, puede expresarse como sigue: (cielo = soleado ∧ humedad = normal) ∨ (cielo = nublado) ∨ (cielo = lluvia ∧ viento = d ébil) 10.2. Problemas apropiados para la aplicación de árboles de decisión Aun cuando se han desarrollado diversos métodos para la inducción de árboles de decisión, y cada uno de ellos ofrece diferentes capacidades, en general estos algoritmos son apropiados para solucionar problemas de aprendizaje conocidos como problemas de clasificación. Estos problemas presentan las siguientes caracterı́sticas: Ejemplos representados por pares atributo-valor. Los casos del problema están representados como un conjunto fijo de atributos, por ejemplo Cielo y sus valores, por ej. Soleado. El caso más sencillo es cuando cada atributo toma valores de un pequeño conjunto discreto y cada valor es disjunto, por ejemplo {Soleado, Nublado, Lluvia}. Existen extensiones para trabajar con atributos de valores reales, por ejemplo, Temperatura expresado numéricamente. La función objetivo tiene valores discretos. El árbol de decisión de la Figura 10.1, asigna una clasificación binaria, por ejemplo si o no a cada caso. Un árbol de decisión puede ser extendido fácilmente, para representar funciones objetivos con más de dos valores posibles. Una extensión menos simple consiste en considerar funciones objetivo de valores discretos, por ello la aplicación del método en dominios discretos es menos común. Se necesitan descripciones disyuntivas. Como se mencionó, los árboles de decisión representan naturalmente conceptos disyuntivos. Ruido en los ejemplos de entrenamiento. El método es robusto al ruido en los ejemplos de entrenamiento, tanto errores de clasificación, como errores en los valores de los atributos. Valores faltantes en los ejemplos. El método puede usarse aún cuando algunos ejemplos de entrenamiento tengan valores desconocidos para algunos atributos. Al igual que en el punto anterior, esto se debe a que el algoritmo computa estadı́sticas globales que minimizan el impacto del ruido o falta de información de un ejemplo. 124 10.3. 10 Arboles de Decisión El algoritmo básico de aprendizaje de árboles de decisión La mayorı́a de los algoritmos para inferir árboles de decisión son variaciones de un algoritmo básico que emplea una búsqueda descendente (top-down) y egoı́sta (greedy) en el espacio de posibles árboles de decisión. La presentación de estos algoritmos se centra en ID3 y C4.5. El algoritmo básico ID3, construye el árbol de decisión de manera descendente, comenzando por preguntarse: Qué atributo deberı́a ser colocado en la raı́z del árbol? Para responder esta pregunta, cada atributo es evaulado usando un test estadı́stico para determinar que tan bien clasifica él solo los ejemplos de entrenamiento. El mejor atributo es seleccionado y colocado en la raı́z del árbol. Una rama y su nodo correspondiente es entonces creada para cada valor posible del atributo en cuestión. Los ejemplos de entrenamiento son repartidos en los nodos descendentes de acuerdo al valor que tengan para el atributo de la raı́z. El proceso entonces se repite con los ejemplos ya distribuidos, para seleccionar un atributo que será colocado en cada uno de los nodos generados. Generalmente, el algoritmo se detiene si los ejemplos de entrenamiento comparten el mismo valor para el atributo que está siendo probado. Sin embargo, otros criterios para finalizar la búsqueda son posibles: i) Covertura mı́nima, el número de ejemplos cubiertos por cada nodo está por abajo de cierto umbral; ii) Pruebas de significancia estadı́stica usando χ 2 para probar si las distribuciones de las clases en los sub-árboles difiere significativamente. Aunque, como veremos, la poda del árbol se prefiere a las pruebas de significancia. Este algoritmo lleva a cabo una búsqueda egoı́sta de un árbol de decisión aceptable, sin reconsiderar nunca las elecciones pasadas (backtracking). Una versión simplificada de él se muestra en el Algoritmo 3. 10.3.1. ¿Qué atributo es el mejor clasificador? La decisión central de ID3 consiste en seleccionar qué atributo colocará en cada nodo del árbol de decisión. En el algoritmo presentado, esta opción la lleva a cabo la función mejor-partición, que toma como argumentos un conjunto de ejemplos de entrenamiento y un conjunto de atributos, regresando la partición inducida por el atributo, que sólo, clasifica mejor los ejemplos de entrenamiento. Considere los ejemplos de entrenamiento del cuadro 10.1 para el concepto objetivo: buen dı́a para jugar tenis? El encabezado del cuadro indica los atributos usados para describir estos ejemplos, siendo jugar-tenis? el atributo objetivo. Si queremos particionar este conjunto de ejemplos con respecto al atributo temperatura, obtendrı́amos: ?- partition(temperatura, Ejemplos). Ejemplos= [[temperatura [frio 5 6 7 9] [caliente 1 2 3 13] [templado 4 8 10 11 12 14]] 10.3 El algoritmo básico de aprendizaje de árboles de decisión 125 Algoritmo 3 El algoritmo ID3 1: function ID3(Ejs, Atbs, Clase) 2: Arbol ← 0; / De f ault ← claseMayoria(E js); 3: if E js = 0/ then 4: return De f ault; 5: else if mismoValor(E js,Clase) then 6: return Arbol ← tomaValor( f irst(E js).Clase); 7: else if Atbs = 0/ then 8: return Arbol ← valorMasComun(E js,Clase); 9: else 10: Me jorParticion ← Me jorParticion(E js, Atbs); 11: Me jorAtributo ← f irst(Me jorParticion); 12: Arbol ← Me jorAtributo; 13: for all ParticionE js ∈ rest(Me jorParticion) do 14: ValoAtributo ← f irst(ParticionE js); 15: SubE js ← rest(ParticionE js); 16: agregarRama(Arbol, alorAtributo, ID3(SubE js, {Atbs Me jorAtributo},Clase)); 17: end for 18: return Arbol 19: end if 20: end function Dı́a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 \ Cielo Temperatura Humedad Viento Jugar-tenis? soleado calor alta débil no soleado calor alta fuerte no nublado calor alta débil si lluvia templado alta débil si lluvia frı́o normal débil si lluvia frı́o normal fuerte no nublado frı́o normal fuerte si soleado templado alta débil no soleado frı́o normal débil si lluvia templado normal débil si soleado templado normal fuerte si nublado templado alta fuerte si nublado calor normal débil si lluvia templado alta fuerte no Cuadro 10.1 Conjunto de ejemplos de entrenamiento para el concepto objetivo jugar-tenis? en ID3, por Tom M. Mitchel [10]. Lo que significa que el atributo temperatura tiene tres valores diferentes en el conjunto de entrenamiento: frı́o, caliente, y templado. Los casos d5, d6, d7, y d9, tienen como valor del atributo temperatura= frı́o. La función mejor-partición encuentra el atributo que mejor separa los ejemplos de entrenamiento de acuerdo al atributo objetivo. En qué consiste una buena medida cuantitativa de la bondad de un atributo? Definiremos una propiedad estadı́stica llamada ganancia de información. 126 10.3.2. 10 Arboles de Decisión Entropı́a y ganancia de información Una manera de cuantificar la bondad de un atributo en este contexto, consiste en considerar la cantidad de información que proveerá este atributo, tal y como ésto es definido en teorı́a de información por Claude E. Shannon [18]. Un bit de información es suficiente para determinar el valor de un atributo booleano, por ejemplo, si/no, verdader/falso, 1/0, etc., sobre el cual no sabemos nada. En general, si los posibles valores del atributo vi , ocurren con probabilidades P(vi ), entonces en contenido de información, o entropia, E de la respuesta actuale está dado por: n E(P(vi ), . . . , P(vn )) = ∑ −P(vi ) log2 P(vi ) i=1 Consideren nuevamente el caso booleano, aplicando esta ecuación a un volado con una moneda confiable, tenemos que la probabilidad de obtener aguila o sol es de 1/2 para cada una: 1 1 1 1 1 1 E( , ) = − log2 − log2 = 1 2 2 2 2 2 2 Ejecutar el volado nos provee 1 bit de información, de hecho, nos provee la clasificación del experimento: si fue aguila o sol. Si los volados los ejecutamos con una moneda cargada que da 99 % de las veces sol, entonces E(1/100, 99/100) = 0,08 bits de información, menos que en el caso de la moneda justa, porque ahora tenemos más evidencia sobre el posible resultado del experimento. Si la probabilidad de que el volado de sol es del 100 %, entonces E(0, 1) = 0 bits de información, ejecutar el volado no provee información alguna. La gráfica de la función de entropı́a se muestra en la figura 10.2. Figura 10.2 Gráfica de la función entropia para clasificaciones booleanas. Consideren nuevamente los ejemplos de entrenamiento del cuadro 10.1. De 14 ejemplos, 9 son positivos (si es un buen dı́a para jugar tenis) y 5 son negativos. La entropia de este conjunto de entrenamiento es: E( 9 5 , ) = 0,940 14 14 10.4 Espacio de hipótesis en el aprendizaje inductivo de árboles de decisión 127 Si todos los ejemplos son positivos o negativos, por ejemplo, pertencen todos a la misma clase, la entropia será 0. Una posible interpretación de ésto, es considerar la entropia como una medida de ruido o desorden en los ejemplos. Definimos la ganancia de información (GI) como la reducción de la entropı́a causada por particionar un conjunto de entrenamiento S, con respecto a un atributo A: |Sv | E(Sv ) v∈A |S| Ganancia(S, A) = E(S) − ∑ Observen que el segundo término de Ganancia, es la entropı́a con respecto al atributo A. Al utilizar esta medida en ID3, sobre los ejemplos del cuadro 10.1, obtenemos: Ganancia de informacion del atributo CIELO : 0.24674976 Ganancia de informacion del atributo TEMPERATURA : 0.029222548 Ganancia de informacion del atributo HUMEDAD : 0.15183544 Ganancia de informacion del atributo VIENTO : 0.048126936 Maxima ganancia de informacion: 0.24674976 Particion: [cielo [soleado 1 2 8 9 11] [nublado 3 7 12 13] [lluvia 4 5 6 10 14]] Esto indica que el atributo con mayor ganancia de información fue cielo, de ahı́ que esta parte del algoritmo genera la partición de los ejemplos de entrenamiento con respecto a este atributo. Si particionamos recursivamente los ejemplos que tienen el atributo cielo = soleado, obtendrı́amos: Ganancia de informacion del atributo TEMPERATURA : 0.5709506 Ganancia de informacion del atributo HUMEDAD : 0.9709506 Ganancia de informacion del atributo VIENTO : 0.01997304 Maxima ganancia de informacion: 0.9709506 Particion: [humedad [normal 11 9] [alta 8 2 1]] Lo cual indica que en el nodo debajo de soleado deberı́amos incluir el atributo humedad. Todos los ejemplos con humedad = normal, tienen valor si para el concepto objetivo. De la misma forma, todos los ejemplos con valor humedad = alta, tiene valor no para el concepto objetivo. Ası́ que ambas ramas descendiendo de nodo humedad, llevarán a clases terminales de nuestro problema de aprendizaje. El algoritmo terminará por construir el árbol de la figura 10.1. 10.4. Espacio de hipótesis en el aprendizaje inductivo de árboles de decisión Como los otros métodos de aprendizaje, ID3 puede concebirse como un proceso de búsqueda en un espacio de hipótesis, para encontrar aquella hipótesis que se ajusta mejor a los datos de entrenamiento. El espacio de hipótesis explorado por ID3 es el espacio de todos los árboles de decisión posibles. El algoritmo lleva a 128 10 Arboles de Decisión cabo una búsqueda de lo simple a lo complejo, comenzando por el árbol vacı́o, para considerar cada vez hipótesis más complejas. La medida ganancia de información guı́a esta búsqueda de ascenso de colina (hill-climbing), como ejemplificamos en la sección anterior. Considerando ID3 en términos de su espacio y estrategias de búsqueda, es posible analizar sus capacidades y limitaciones: El espacio de hipótesis de ID3 es completo con respecto a las funciones de valores discretos que pueden definirse a partir de los atributos considerados. De manera que no existe el riesgo que la función objetivo no se encuentre en el espacio de hipótesis. ID3 mantiene sólo una hipótesis mientras explora el espacio de hipótesis posibles. Esto contrasta, por ejemplo, con el algoritmo eliminación de candidatos, que mantiene el conjunto de todas las hipótesis consistentes con el conjunto de entrenamiento. Es por ello que ID3 es incapaz de determinar cuantos árboles de decisión diferentes son consistentes con los datos. El algoritmo básico ID3 no ejecuta vuelta atrás (backtracking) en su búsqueda. Una vez que el algoritmo selecciona un atributo, nunca reconsiderará esta elección. Por lo tanto, es suceptible a los mismos riesgos que los algoritmos estilo ascenso de colina, por ejemplo, caer máximos o mı́nimos locales. Como veremos, la vuelta atrás puede implementarse con alguna técnica de poda. ID3 utiliza todos los ejemplos de entrenamiento en cada paso de su búsqueda guiada por el estadı́stico ganancia de información. Esto contrasta con los métodos que usan los ejemplos incrementalmente, por ejemplo encuentra-S o eliminación de candidatos. Una ventaja de usar propiedades estadı́sticas de todos los ejemplos es que la búsqueda es menos sensible al ruido en los datos. 10.5. Sesgo inductivo en el aprendizaje de árboles de decisión Recuerden que el sesgo inductivo es el conjunto de afirmaciones que, junto con los datos de entrenamiento, justifican deductivamente la clasificación realizada por un sistema de aprendizaje inductivo sobre casos futuros. Dado un conjunto de entrenamiento, por lo general hay muchos árboles de decisión consistentes con éste. Describir el sesgo inductivo de ID3 equivale a explicar porqué este algoritmo prefiere ciertos árboles a otros, qué árbol eligirá. Puesto que ID3 encontrará el primer árbol consistente con el conjunto de entrenamiento, producto de una búsqueda de ascenso de colina, de lo simple a lo complejo, el algoritmo tiene preferencia por: i) árboles pequeños sobre árboles grandes, que indican que la búsqueda termino en proximidad a la raı́z del árbol; y ii) debido a su caracter egoista, árboles que colocan atributos más informativos cerca de la raı́z del árbol. Sin embargo, observen que este sesgo es aproximado. Un algoritmo que tuviera un sesgo idéntico al descrito aquı́, tendrı́a que realizar una búsqueda primero en amplitud y preferir los árboles de menor profundidad. ID3 busca primero en profundidad. 10.6 Consideraciones sobre el aprendizaje inductivo de árboles de decisión 10.5.1. 129 Sesgo por restricción y sesgo por preferencia Existe una diferencia interesante entre los sesgos que exhiben ID3 y el algoritmo eliminación de candidatos, discutido en la sesión anterior. El sesgo de ID3 es producto de su estratégia de búsqueda, mientras que el sesgo de eliminación de candidatos es resultado de la definición del espacio de búsqueda. Por lo tanto, el sesgo de ID3 es exhibe una preferencia por ciertas hipótesis, sobre otras, por ejemplo, hipótesis compactas. Este tipo de sesgo, que no impone restricciones sobre las hipótesis que serán eventualmente consideradas, recibe el nombre de sesgo por preferencia. Por otra parte, el sesgo de eliminación de candidatos que restringe el conjunto de hipótesis a considerar, recibe el nombre de sesgo por restricción o sesgo del lenguaje. En general, es preferible trabajar con un sesgo por preferencia, puesto que éste permite al sistema de aprendizaje explorar un espacio de hipótesis completo, asegurando que la representación del concepto objetivo se encuentra ahı́. Consideren que es posible trabajar con ambos sesgos a la ves, por ejemplo, el sistema aprendiz de damas chinas de la sesión de introducción, introduce un sesgo por restricciones cuando se decide que la hipótesis tiene la forma de una combinación lineal de los atributos del tablero, y un sesgo por preferencia cuando se introduce la búsqueda ordenada por mı́nimos cuadrados (LMS) en el espacio de posibles parámetros wi . 10.5.2. ¿Porqué preferir hipótesis más compactas? Es el sesgo inductivo de ID3, preferir las hipótesis más compactas, lo suficientemente robusto para generalizar más allá de los datos observados? Este es un debate no resuelto iniciado por William de Occam1 circa 1320. Un argumento intuitivo es que existen mucho menos hipótesis compactas que extensas, por lo que es más difı́cil que una hipótesis compacta coincida accidentalmente con los datos observados. En cambio, hay muchas hipótesis extensas que se puede, ajustar a los datos de entrenamiento, pero fallarán al generalizar. Aunque este argumento no es del todo convincente, dejaremos la discusión sobre la navaja de Occam para la sesión destinada a aprendizaje Bayesiano. 10.6. Consideraciones sobre el aprendizaje inductivo de árboles de decisión Algunas consideraciones sobre la aplicación práctica del algoritmo básico de ID3 presentado aquı́, incluyen: mecanismos para determinar que tanto debe crecer el 1 El enunciado exacto de la navaja de Occam es: Non sunt multiplicanda entia prater necessitatem (las entidades no deben multiplicarse más allá de lo necesario). 130 10 Arboles de Decisión árbol en profundidad; para procesar atributos con valores contı́nuos; para procesar ejemplos de entrenamiento con valores faltantes; para introducir costos diferentes asociados a los atributos; ası́ como para determinar una buena métrica de selección de los atributos y mejorar la eficiencia computacional del algoritmo. Cabe mencionar que, muchos de estos aspectos han sido incorporados en el sistema C4.5 [14]. 10.6.1. Evitando un sobreajuste con los datos de entrenamiento El algoritmo básico de ID3 crece cada rama del árbol en profundidad hasta que logra clasificar perfectamente los ejemplos de entrenamiento. Esta estrategia es razonable, pero puede introducir dificultades si los datos de entrenamiento presentan ruido, o cuando el conjunto de entrenamiento es demasiado pequeño, como para ofrecer un muestreo significativo del concepto objetivo. En estos casos, ID3 puede producir árboles que se sobreajustan a los datos de entrenamiento. Formalmente definimos el sobreajuste como: Definition 10.1. Dado un espacio de hipótesis H, se dice que una hipótsis h ∈ H está sobreajustada a los ejemplos de entrenamiento, si existe una hipótesis alternativa h0 ∈ H, tal que h0 tiene un error de clasificación más pequeño que h sobre la distribución completa de los casos del problema. Es común observar que a medida que el tamaño del árbol crece, en término del número de nodos usado2 , su precisión sobre el conjunto de entrenamiento mejora monotonicamente, pero, sobre el conjunto de prueba primero crece y luego decae. Como es esto posible que un árbol h que tiene mayor precisión que h0 sobre el conjunto de entrenamiento, luego tenga un desempeño menor sobre el conjunto de prueba? Una situación en la que esto ocurre es cuando el conjunto de entremiento contiene ruido, por ejemplo, elementos mal clasificados. Consideren agregar el siguiente caso mal clasificado (clasificado como jugar-tenis? = no) al conjunto de entrenamiento del cuadro 10.1: h cielo = soleado,temperatura = caliente, humedad = normal, viento = f uerte i Al ejecutar ID3 sobre el nuevo conjunto de entrenamiento, éste construirá un árbol más complejo. En partı́cular, el ejemplo con ruido será filtrado junto con los ejemplos d9 y d11 (cielo = soleado y humedad = normal), que son ejemplos positivos. Dado que el nuevo ejemplo es negativo, ID3 buscará refinar el árbol a partir del nodo humedad, agregando un atributo más al árbol. Este nuevo árbol h0 tiene mayor precisión sobre los ejemplos de entrenamiento que h, puesto que se ha ajustado al ejemplo con ruido. Pero h tendrá mejor desempeño al clasificar nuevos casos, tomados de una misma distribución que los ejemplos de entrenamiento. 2 Obseven que esto refleja el número de atributos usado en la hipótesis, esto es, árboles más grandes imponen más restricciones. 10.6 Consideraciones sobre el aprendizaje inductivo de árboles de decisión 131 Existe la posibilidad de sobreajuste, aún cuando el conjunto de entrenamiento esté libre de ruido, por ejemplo, si el conjunto de entrenamiento tiene pocos elementos. En conjuntos de entrenamiento pequeños es fácil encontrar regularidades accidentales en donde un atributo puede particionar muy bien los ejemplos dados, aunque no esté relacionado con el concepto objetivo. Puesto que el sobreajuste puede reducir la precisión de un árbol inducido por ID3 entre un 10 a 25 %, diferentes enfoques han sido propuestos para evitar este fenómeno. Los enfoques pueden agruparse en dos clases: Enfoques que detienen el crecimiento del árbol anticipadamente, antes de que alcance un punto donde clasifique perfectamente los ejemplos de entrenamiento. Enfoques en donde se deja crecer el árbol para después podarlo. Aunque el primer enfoque parezca más directo, la poda posterior del árbol ha demostrado tener más éxito en la práctica. Esto se debe a la dificultad de estimar en que momento debe detenerse el crecimiento del árbol. Independientemente del enfoque usado, una pregunta interesante es: ¿Cual es el tamaño correcto de un árbol? Algunos enfoques para responder a esta pregunta incluyen: Usar un conjunto de ejemplos, diferentes de los usados en el entrenamiento, para evaluar la utilidad de eliminar nodos del árbol. Usar los ejemplos disponibles para el entrenamiento, pero aplicando una prueba para estimar cuando agregar o eliminar un nodo, podrı́a producir una mejora al clasificar nuevos casos, por ejemplo, usar el test χ 2 para evaluar si al expandir un nodo, el cambio mejorará la clasificación sobre los ejemplos de entrenamiento, o sobre toda la distribución. Usar explı́citamente una medida de complejidad para codificar los ejemplos de entrenamiento y el árbol de decisión, deteniéndo el crecimiento cuando el tamaño codificado sea minimizado. Por ejemplo, el principio de descripción mı́nima (MDL). 10.6.1.1. Reduciendo el error por poda ¿Como podemos usar un conjunto de ejemplos de validación para prevenir el sobre ajuste? Un enfoque llamado reduced-error pruning [?], consiste en considerar cada nodo del árbol como candidato a ser podado. La poda consiste en eliminar todo el subárbol que tiene como raı́z el nodo en cuestión, convirtiéndolo ası́ en una hoja, cuya clase corresponde a valor más común de los casos asociados a ese nodo. Un nodo solo es eliminado si el árbol podado que resulta de ello, no presenta un desempeño peor que el árbol original sobre el conjunto de validación. El efecto de esto, es que los nodos que se han colocado en el árbol por coincidencias fortuitas en los datos del entrenamiento, generalmente son eliminados debido a que las coincidencias suelen no estar presentes en el conjunto de validación. Este método es unicamente efectivo si contamos con suficientes ejemplos, de tal forma que el conjunto de entrenamiento y el conjunto de validación sean significativos estadı́sticamente. De otra forma, tomar ejemplos para el conjunto de validación 132 10 Arboles de Decisión reduce aún más el tamanõ del conjunto de entrenamiento, aumentando ası́ la posibilidad de sobre ajuste. 10.6.1.2. Poda de reglas En la práctica, un método exitoso para encontrar el árbol de mayor precisión se conoce como rule post-prunning [14] y está incorporado en el sistema C4.5 de Ross Quinlan. El procedimiento es el siguiente: 1. Inducir el árbol de decisión permitiendo sobre ajuste, por ejemplo, con nuestro algoritmo básico ID3. 2. Convertir el árbol aprendido en un conjunto de reglas equivalente, esto es, una conjunción por cada rama del árbol que va de la raı́z a una hoja. 3. Podar (generalizar) cada regla, eliminando las precondiciones que resulten en una mejora de la precisión estimada. 4. Ordenar las reglas por su precisión estimada, y aplicarlas en ese orden al clasificar nuevos casos. Cabe mencionar que el método aplicado por C4.5 no es estadı́sticamente valido, aunque ha demostrado ser una heurı́stica útil. En la sesión de evaluación de hipótesis, estudiamos técnicas estadı́sticamente robustas para estimar medias e intervalos de confianza. Lo relevante aquı́ es que la conversión del árbol en reglas ayuda a distinguir los diferentes contextos en los que un atributo participa en la clasificación, es decir, reglas diferentes; elimina la diferencia entre nodos ubicados cerca de la raı́z y aquellos ubicados cerca de las hojas; y aumenta la fácilidad de comprehensión por parte del usuario. 10.6.2. Incorporando valores contı́nuos En el algoritmo básico de ID3 tanto el concepto objetivo, como los atributos usados para describir los casos, deben tener valores discretos. La segunda restricción puede ser eliminada fácilmente, permitiendo el uso de atributos con valores contı́nuos. Esto se logra definiéndo dinámicamente nuevos atributos discretos que particionan los atributos de valores contı́nuos, en intervalos discretos. Para un atributo contı́nuo A, el algoritmo puede crear dinámicamente un atributo discreto Ac que es verdadero si A > c y falso en cualquier otro caso. La única consideración es como seleccionar el mejor valor para el umbral c. Supongan que el atributo temperatura toma valores discretos y que su relación con el concepto objetivo es la siguiente: temperatura 40 48 60 72 80 90 jugar-tenis? No No Si Si Si No Qué valor booleano basado en un umbral debemos definir para el atributo temperatura? Obviamente, necesitamos un umbral c, tal que éste produzca la mayor 10.6 Consideraciones sobre el aprendizaje inductivo de árboles de decisión 133 ganancia de información posible. Es posible generar candidatos a umbral, ordenando los ejemplos de acuerdo a su valor en el atributo temperatura e identificando ejemplos adyacentes que difieren en el valor de su atributo objetivo. Se puede demostrar que los umbrales c que máximiza la ganancia de información, se encuentran en estos sitios. Para el ejemplo presentado, dos umbrales pueden localizarse en los puntos (48 + 60)/2 y (80 + 90/2). La ganancia de información puede entonces calcularse para los atributos temperatura>54 y temperatura>85 . El atributo con mayor ganancia de información, en este caso el primero, puede ser usado entonces para competir con otros atributos en la construcción del árbol de decisión. Por supuesto, es posible también mantener ambos atributos dinámicamente creados, usando multiples intervalos [?]. 10.6.3. Medidas alternativas para la selección de atributos Existe un sesgo natural en la medida de ganancia de información, el cual favorece atributos con muchos valores, sobre aquellos que tienen poco valores. Por ejemplo, un atributo fecha, tendrı́a mayor ganancia de información que cualquiera de los atributos en nuestro ejemplo. Esto se debe a que este atributo predice perfectamente el valor del atributo objetivo. El problema es que este atributo tiene tantos valores distintos que tiende a separar perfectamente los ejemplos de entrenamiento en pequeños subconjuntos, que se ajustan al concepto buscado. Por esto, el atributo fecha tiene una ganancia de información elevada, a pesar de ser un predictor pobre. Una solución a este problema es usar una métrica alternativa a la ganancia de información. Quinlan [?], propone una medida alternativa que ha sido usada con éxito, gain ratio. Esta métrica penaliza atributos como fecha incorporando un término conocido como split information, que es sensible a qué tan amplia y uniforme es la partición que un atributo induce en los datos: c |Si| |Si| log2 |S| |S| i=1 splitIn f ormation(S, A) = − ∑ Observen que este término es la entropia de S con respecto al atributo A. La medida gain radio está definida como: gainRatio(S, A) = gain(S, A) splitIn f ormatio(S, A) Un problema práctico con este enfoque es que el denominador de esta medida puede ser 0 o muy pequeño, si |Si| ≈ |S|, lo cual hace que la medida sea indefinida para atributos que tienen casi el mismo valor para todos los ejemplos. 134 10.7. 10 Arboles de Decisión Implementación el Prolog Los ejemplos de entrenamiento se definirán mediante el predicado example/3 cuyos argumentos son el número de ejemplo, el valor para la clase, y los pares atributo valor. Para el ejemplo de jugar tenis, el conjunto de entrenamiento incluirá las siguientes lı́neas: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ejemplo(1,no,[dia=soleado,temperatura=calor,humedad=alta,viento=debil]). ejemplo(2,no,[dia=soleado,temperatura=calor,humedad=alta,viento=fuerte]). ejemplo(3,si,[dia=nublado,temperatura=calor,humedad=alta,viento=debil]). ejemplo(4,si,[dia=lluvia,temperatura=templado,humedad=alta,viento=debil]). ejemplo(5,si,[dia=lluvia,temperatura=frio,humedad=normal,viento=debil]). ejemplo(6,no,[dia=lluvia,temperatura=frio,humedad=normal,viento=fuerte]). ejemplo(7,si,[dia=nublado,temperatura=frio,humedad=normal,viento=fuerte]). ejemplo(8,no,[dia=soleado,temperatura=templado,humedad=alta,viento=fuerte]). ejemplo(9,si,[dia=soleado,temperatura=frio,humedad=normal,viento=debil]). ejemplo(10,si,[dia=lluvia,temperatura=templado,humedad=normal,viento=debil]). ejemplo(11,si,[dia=soleado,temperatura=nublado,humedad=normal,viento=fuerte]). ejemplo(12,si,[dia=nublado,temperatura=templado,humedad=alta,viento=fuerte]). ejemplo(13,si,[dia=nublado,temperatura=calor,humeda=normal,viento=debil]). ejemplo(14,no,[dia=lluvia,temperatura=templado,humedad=alta,viento=fuerte]). El árbol se representará mediante el predicado nodo/3 cuyo primer elemento puede ser el identificador de un ejemplo, o la constante ho ja para indicar que hemos encontrado el valor de la clase. El segundo argumento de nodo es el test que se aplica para llegar a él. El tercer argumento es el padre del nodo en cuestión. Por ejemplo: 1 nodo(13,(dia=lluvia),raiz). indica que al ejemplo 13 se llega desde el nodo raiz si el atributo dia tiene como valor lluvia. La llamada principal al algoritmo es id3/1 cuyo argumento es el mı́nimo número de casos que debe cubrir una hoja del árbol: 1 id3 :- id3(1). % Umbral = 1, por default. 2 3 4 5 6 7 8 id3(Umbral) :retractall(nodo(_,_,_)), findall(N,ejemplo(N,_,_),E), ejemplo(_,_,L), !, atributos(L,A), idt(E,raiz,A,Umbral), !. Con la llamada a id3 se borran los nodos construidos anteriormente y se construye una nueva lista de ejemplos E. Posteriormente se obtiene la lista de atributos usados en los ejemplos A. El corte es para construir la lista de atributos solo con un ejemplo (evita el reconsiderar con los demás ejemplos). Y se llama a idt/4 cuyos argumentos son la lista de ejemplos E, el identificador raiz para el nodo raı́z del árbol, la lista de atributos A y el Umbral que por defecto es igual a 1. 10.7 Implementación el Prolog 135 En la construcción del árbol hay varios casos que cubrir. Comencemos por los casos terminales. Hay dos casos terminales a considerar, el primero es cuando el número de ejemplos disponibles es menor que el Umbral en ese caso se guarda un nodo ho ja con la distribución de la clase para los ejemplos como contenido. El otro caso terminal es cuando todos los ejemplos pertenecen a la misma clase. En ese caso la distribución de la clase para los ejemplos tomara la forma [C] donde C = Clase/NumE js. Estos dos casos corresponde a: 1 2 3 4 5 idt(E,Padre,_,Umbral) :length(E,Lon), Lon=<Umbral, distr(E, Distr), assertz(nodo(hoja,Distr,Padre)), !. 6 7 8 9 idt(E,Padre,_,_) :distr(E, [C]), assertz(nodo(hoja,[C],Padre)). Si no estamos en el caso terminal, es necesario elegir el mejor atributo y particionar los ejemplos de acuerdo a los valores para el atributo seleccionado: 1 2 3 idt(Es,Padre,As,Umbral) :elige_atributo(Es,As,A,Valores,Resto), !, particion(Valores,A,Es,Padre,Resto,Umbral). Si esto no es posible, es que los datos son inconsistentes: 1 2 3 4 5 idt(E,Padre,_,_) :- !, nodo(Padre,Test,_), write(’Datos inconsistentes: no es posible construir partición de ’), write(E), write(’ en el nodo ’), writeln(Test). 10.7.1. Atributos Veamos ahora en detalle estos procedimientos. El siguiente procedimiento extrae los atributos de un ejemplo: 1 2 3 atributos([],[]) :- !. atributos([A=_|T],[A|W]) :atributos(T,W). De forma que, para el caso de jugar tenis, los atributos se pueden obtener con la siguiente llamada: 136 1 2 3 10 Arboles de Decisión ?- ejemplo(_,_,L), !, atributos(L,A). L = [dia=soleado, temperatura=calor, humedad=alta, viento=debil], A = [dia, temperatura, humedad, viento]. 10.7.2. Distribución de clases ¿Cual es la distribución inicial de la clase para los ejemplos de jugar tenis? Esto lo podemos consultar con: 1 2 3 ?- findall(E,ejemplo(E,_,_),Ejs), distr(Ejs,Dist). Ejs = [1, 2, 3, 4, 5, 6, 7, 8, 9|...], Dist = [no/5, si/9]. lo que indica que tenemos 5 ejemplos de la clase no y 9 de la clase si. Los ejemplos 1,2,6 y 8 son todos miembros de la clase no, por lo que: 1 2 ?- distr([1,2,6,8],Dist). Dist = [no/4]. La implementación de distr/2 es la siguiente: 1 2 3 distr(S,Dist) :setof(C,XˆLˆ(member(X,S),ejemplo(X,C,L)),Cs), cuentac(Cs,S,Dist). 4 5 cuentac([],_,[]) :- !. 6 7 8 9 10 cuentac([C|L],E,[C/N|T]) :findall(X,(member(X,E),ejemplo(X,C,_)),W), length(W,N), !, cuentac(L,E,T). 10.7.3. El mejor atributo El mejor atributo es el que maximiza la ganancia de información Gain con respecto a los ejemplos Es y atributos As disponibles. El predicado elige atributo/4 computa los valores posibles para el atributo seleccionado y lo elimina de la lista de atributos disponibles para construir el árbol: 1 2 elige_atributo(Es,As,A,Valores,Resto) :length(Es,LonEs), 10.7 Implementación el Prolog 3 4 5 6 7 8 9 10 11 12 137 contenido_informacion(Es,LonEs,I), !, findall((A-Valores)/Gain, (member(A,As), valores(Es,A,[],Valores), separa_en_subconjs(Valores,Es,A,Ess), informacion_residual(Ess,LonEs,R), Gain is I - R), All), maximo(All,(A-Valores)/_), eliminar(A,As,Resto), !. 13 14 15 16 17 separa_en_subconjs([],_,_,[]) :- !. separa_en_subconjs([V|Vs],Es,A,[Ei|Resto]) :subconj(Es,A=V,Ei), !, separa_en_subconjs(Vs,Es,A,Resto). 18 19 20 21 22 23 24 informacion_residual([],_,0) :- !. informacion_residual([Ei|Es],Lon,Res) :length(Ei,LonEi), contenido_informacion(Ei,LonEi,I), !, informacion_residual(Es,Lon,R), Res is R + I*LonEi/Lon. 25 26 27 28 contenido_informacion(Es,Lon,I) :setof(C,EˆLˆ(member(E,Es),ejemplo(E,C,L)),Classes), !, suma_terms(Classes,Es,Lon,I). 29 30 31 32 33 34 35 suma_terms([],_,_,0) :- !. suma_terms([C|Cs],Es,Lon,Info) :findall(E,(member(E,Es),ejemplo(E,C,_)),InC), length(InC,N), suma_terms(Cs,Es,Lon,I), Info is I - (N/Lon)*(log(N/Lon)/log(2)). 36 37 38 39 40 41 42 43 valores([],_,Valores,Valores) :- !. valores([E|Es],A,Vs,Valores) :ejemplo(E,_,L), member(A=V,L), !, (member(V,Vs), !, valores(Es,A,Vs,Valores); valores(Es,A,[V|Vs],Valores) ). 44 45 46 47 48 49 50 subconj([],_,[]) :- !. subconj([E|Es],A,[E|W]) :ejemplo(E,_,L), member(A,L), !, subconj(Es,A,W). subconj([_|Es],A,W) :- subconj(Es,A,W). Por ejemplo, la siguiente meta computa el mejor atributo (dia), dados los ejemplos E y atributos conocidos: 138 10 Arboles de Decisión ?- findall(N,ejemplo(N,_,_),E), elige_atributo(E,[dia,temperatura,humedad, viento],A,V,R). E = [1, 2, 3, 4, 5, 6, 7, 8, 9|...], A = dia, V = [lluvia, nublado, soleado], R = [temperatura, humedad, viento]. Como vimos en la primera parte de este capı́tulo, para computar la ganancia de información necesitamos computar el contenido informacional de todos los ejemplos: ?- findall(N,ejemplo(N,_,_),E), length(E,L), contenido_informacion(E,L,I). E = [1, 2, 3, 4, 5, 6, 7, 8, 9|...], L = 14, I = 0.940286. Y por cada atributo, computar la información residual para restarsela al contenido informacional, y ası́ obtener las ganancias de información: ?- findall(N,ejemplo(N,_,_),E), findall((A-Valores)/Gain, (member(A,[dia,temperatura,humedad,viento]), valores(E,A,[],Valores), separa_en_subconjs(Valores,E,A,Ess), informacion_residual(Ess,14,R), Gain is 0.940286 - R), All). E = [1, 2, 3, 4, 5, 6, 7, 8, 9|...], All = [ (dia-[lluvia, nublado, soleado])/0.24675, (temperatura-[nublado, frio, templado, calor])/0.0760099, (viento-[fuerte, debil])/0.151836 ]. Solo resta obtener el atributo de All con la máxima ganancia de información y eliminarlo de la lista de atributos disponibles regresada en Resto. La inducción del árbol de decisión es un proceso recursivo. Los valores del atributo elegido inducen una partición sobre los ejemplos. Esto se logra con el predicado particion/6. 1 2 3 4 5 6 7 particion([],_,_,_,_,_) :- !. particion([V|Vs],A,Es,Padre,Resto,Umbral) :subconj(Es,A=V,Ei), !, genera_nodo_id(Nodo), assertz(nodo(Nodo,A=V,Padre)), idt(Ei,Nodo,Resto,Umbral), !, particion(Vs,A,Es,Padre,Resto,Umbral). 10.7.4. El árbol El árbol se guarda en la memoria de trabajo como un conjunto de nodos con la forma nodo(Identi f icador, Atributo −Valor, NodoPadre). Se pueden recuperar con la siguiente meta: 10.7 Implementación el Prolog ?- nodo(I,AV,P). I = 1, AV = (dia=lluvia), P = raiz ; I = 2, AV = (viento=fuerte), P = 1 ; I = hoja, AV = [no/2], P = 2 ; I = 3, AV = (viento=debil), P = 1 ; I = hoja, AV = [si/3], P = 3 ; I = 4, AV = (dia=nublado), P = raiz ; I = hoja, AV = [si/4], P = 4 ; I = 5, AV = (dia=soleado), P = raiz ; I = 6, AV = (humedad=normal), P = 5 ; I = hoja, AV = [si/2], P = 6 ; I = 7, AV = (humedad=alta), P = 5 ; I = hoja, AV = [no/3], P = 7. 10.7.5. Imprimiendo el árbol construido. Para imprimir el árbol construido de una manera legible definimos: 1 2 imprime_arbol :imprime_arbol(raiz,0). 3 4 5 6 7 8 9 10 imprime_arbol(Padre,_) :nodo(hoja,Class,Padre), !, write(’ => ’),write(Class). imprime_arbol(Padre,Pos) :findall(Son,nodo(Son,_,Padre),L), Pos1 is Pos+2, imprime_lista(L,Pos1). 11 12 imprime_lista([],_) :- !. 13 14 imprime_lista([N|T],Pos) :- 139 140 15 16 17 18 10 Arboles de Decisión node(N,Test,_), nl, tab(Pos), write(Test), imprime_arbol(N,Pos), imprime_lista(T,Pos). 10.7.6. Ejecutando todo el experimento Ası́ la sesión para construir el árbol de decisión para jugar tenis es (con id3.pl ya cargado en Prolog): ?- [test3]. % test3 compiled 0.00 sec, 3,896 bytes true. ?- id3. true. ?- imprime_arbol. dia=lluvia viento=fuerte => [no/2] viento=debil => [si/3] dia=nublado => [si/4] dia=soleado humedad=normal => [si/2] humedad=alta => [no/3] true. 10.7.7. Predicados auxiliares Algunos predicados auxiliares incluyen: 1 2 3 4 genera_nodo_id(M) :retract(id(N)), M is N+1, assert(id(M)), !. 5 6 7 genera_nodo_id(1) :assert(id(1)). 8 9 eliminar(X,[X|T],T) :- !. 10 11 12 eliminar(X,[Y|T],[Y|Z]) :eliminar(X,T,Z). 13 14 subconjunto([],_) :- !. 15 16 subconjunto([X|T],L) :- 10.7 Implementación el Prolog 17 18 member(X,L), !, subconjunto(T,L). 19 20 21 22 23 maximo([X],X) :- !. maximo([X/M|T],Y/N) :maximo(T,Z/K), (M>K,Y/N=X/M ; Y/N=Z/K), !. 141 Capı́tulo 11 Planeación Resumen La planeación es un tema de interés tradicional en Inteligencia Artificial, que involucra razonar acerca de los efectos de las acciones y la secuencia en que estas se aplican para lograr un efecto acumulativo dado. En esta sesión desarrollaremos planificadores simples para ilustrar los principios de la planeación. 11.1. Acciones Cada acción posible es definida en términos de su condición y sus efectos, especı́ficamente: Condición. La condición que debe satisfacerse para la acción pueda ejecutarse. Agregar. Es una lista de cambios que se espera ocurran después de ejecutarse la acción. Quitar. Es una lista de observaciones que se espera dejen de ser verdaderas después de ejecutarse la acción. Las condiciones, pueden definirse por un procedimiento 1 cond(Acc,Cond). Los efectos de una acción pueden definirse de manera similar, por dos procedimientos: 1 2 add(Acc,ListaAdd). del(Acc,ListaDel). donde ListaAdd y ListaDel corresponden a las listas definidas para agregar y borrar. Asumamos que vamos a realizar la planeación en el dominio del mundo de los bloques. Ası́ que la única acción posible será: 143 144 1 11 Planeación mover(Bloque,De,A). La definición completa de esta acción es como sigue: 1 2 3 4 5 6 7 8 9 precond( mover( Bloque, De, A), [ despejado( Bloque), despejado( A), en( Bloque, De)] ) :bloque( Bloque), objeto( A), A \== Bloque, objeto( De), De \== A, Bloque \== De. 10 11 agregar( mover(X,De,A), [ en(X,A), despejado(De)]). 12 13 borrar( mover(X,De,A), [ en(X,De), despejado(A)]). De manera que para poder mover un bloque Bloque de la posición De a la posición A, es necesario que el bloque Bloque y la posición A estén despejados, lo mismo que el bloque Bloque esté en la posición De. El resto del procedimiento cond/2 establece restricciones extras: que Bloque sea un bloque, y A y De sean objetos en el universo de discurso; que A sea diferente de Bloque (no mover el bloque sobre si mismo); que se debe mover el bloque a una nueva posición (A es diferente de De); y no mover el bloque de sı́ mismo (Bloque es diferente de De). Las definiciones de add/2 y del/2 completan la especificación de mover/3. Las siguientes definiciones especifican un escenario en el mundo de los bloques: 1 2 3 4 objeto( X) :lugar( X) ; bloque( X). 5 6 7 8 bloque( a). bloque( b). bloque( c). 9 10 11 12 13 lugar( lugar( lugar( lugar( 1). 2). 3). 4). 14 15 16 estado1( [ despejado(2), despejado(4), despejado(b), despejado(c), en(a,1), en(b,3), en(c,a) ] ). 17 18 metas1([en(a, b)]). 11.2 Análisis medios-fines 145 Tal definición de las acciones, establece también el espacio de planes posibles, por lo que se le conoce como espacio de planeación. Las metas del planeador se definen en términos de una lista de observaciones que se deben cumplir. Ahora veremos como a partir de esta representación, es posible derivar los planes mediante un procedimiento conocido como análisis medios-fines. 11.2. Análisis medios-fines Consideremos que el mundo de los bloques se encuentra en el estado inicial especificado anteriormente (estado1). Sea la meta del planeador en(a, b). El trabajo del planeador consiste en encontrar una secuencia de acciones que satisfagan esta meta. Un planeador tı́pico razonarı́a de la siguiente forma: 1. Encontrar una acción que satisfaga en(a, b). Al buscar en la relación add, encontramos que tal acción es de la forma mover(a, De, b) a partir de cualquier De. Tal acción deberá formar parte de nuestro plan, pero no podemos ejecutarla inmediatamente dado nuestro estado inicial. 2. Hacer posible la acción mover(a, De, b). Al buscar en la relación cond encontramos que la condición para ejecutar esta acción es: 1 [ despejado(a), despejado(b), en(a,De) ] en el estado inicial tenemos que despe jado(b) y que en(a, De) para De/1; pero no que despe jado(a), ası́ que el planeador se concentra en esta fórmula como su nueva meta. 3. Volvemos a buscar en la relación add para encontrar una acción que satisfaga despe jado(a). Tal acción tiene la forma mover(Bloque, a, A). La condición para ejecutar esta acción es: 1 [despejado(Bloque), despeado(A), en(Bloque,a) ] la cual se satisface en nuestro estado inicial para Boque/c y A/2. De forma que mover(c, a, 2) puede ejecutarse en el estado inicial, modificando el estado del problema de la siguiente manera: Eliminar del estado inicial las relaciones que la acción borra. Incluir las relaciones que la acción agrega al estado inicial del problema. esto produce la lista: 1 2 [ despejado(a), despejado(b), despejado(c), despejado(4), en(a,1), en(b,3), en(c,2) ] 4. Ahora podemos ejecutar la acción mover(a, 1, b), con lo que la meta plantada se satisface. El plan encontrado es: 146 11 Planeación [ mover(c,a,2), mover(a,1,b) ] 1 Este estilo de razonamiento se conoce como análisis medios-fines. Observen que el ejemplo planteado el plan se encontró directamente, sin necesidad de reconsiderar. Esto ilustra como el proceso de razonar sobre el efecto de las acciones y las metas guı́an la planeación en una dirección adecuada. Desafortunadamente, no siempre se puede evitar reconsiderar. De hecho, la explosión combinatoria y la búsqueda son tı́picas en la planeación. El principio de planeación por análisis medios-fines se ilustra en la figura 11.1. Puede plantearse como sigue: Para resolver una lista de metas Metas en un estado Estado, que lleven a un estado Estado final, hacer: Si todas las Metas son verdaderas en Estado, entonces Estado final = Estado. En cualquier otro caso: 1. 2. 3. 4. Seleccionar una Meta no solucionada en Metas. Encontrar una Acción que agregue Meta al estado actual. Hacer posible Acción resolviendo Condición para obtener el estado inter 1. Aplicar la Acción en el estado inter 1 para obtener el estado inter 2 donde Meta se cumple. 5. Resolver Metas en el estado inter 2 para llegar a Estado final. Condición prePlan Estado Meta Acción Estado inter 1 Metas postPlan Estado inter 2 Estado final Figura 11.1 Análisis medios-fines El código del planeador medios fines es como sigue: 1 2 plan( Estado, Metas, [], Estado) :satisfecho( Estado, Metas). 3 4 5 6 7 8 9 10 11 12 plan( Estado, Metas, Plan, EstadoFinal) :append( PrePlan, [Accion | PostPlan], Plan), seleccionar( Estado, Metas, Meta), lograr( Accion, Meta), precond( Accion, Condicion), plan( Estado, Condicion, PrePlan, EstadoInter1), aplicar( EstadoInter1, Accion, EstadoInter2), plan( EstadoInter2, Metas, PostPlan, EstadoFinal). 11.3 Metas protegidas 13 147 satisfecho( _, []). 14 15 16 17 satisfecho( Estado, [Meta | Metas]) member( Meta, Estado), satisfecho( Estado, Metas). :- 18 19 20 21 seleccionar( Estado, Metas, Meta) :member( Meta, Metas), not(member( Meta, Estado)). 22 23 24 25 lograr( Accion, Meta) :agregar( Accion, Metas), member( Meta, Metas). 26 27 28 29 30 31 aplicar( Estado, Accion, NewEstado) :borrar( Accion, ListaBorrar), borrar_todos( Estado, ListaBorrar, Estado1), !, agregar( Accion, ListaAgregar), append( ListaAgregar, Estado1, NewEstado). 32 33 borrar_todos( [], _, []). 34 35 36 37 borrar_todos( [X | L1], L2, Diff) :member( X, L2), !, borrar_todos( L1, L2, Diff). 38 39 40 borrar_todos( [X | L1], L2, [X | Diff]) borrar_todos( L1, L2, Diff). :- Para invocar al planeador, ejecutamos en Prolog la siguiente meta: 1 2 3 4 5 6 7 ?- estado1(E), metas1(M), plan(E,M,P,Efinal). E = [despejado(2), despejado(4), despejado(b), despejado(c), en(a, 1), en(b, 3), en(c, a)], M = [en(a, b)], P = [mover(c, a, 2), mover(a, 1, b)], Efinal = [en(a, b), despejado(1), en(c, 2), despejado(a), despejado(4), despejado(c), en(b, 3)] 11.3. Metas protegidas Consideren ahora la siguiente llamada a plan/4: 1 2 3 4 5 ?- estado1(E), plan(E,[en(a,b),en(b,c)],Plan,_). E = [despejado(2), despejado(4), despejado(b), despejado(c), en(a, 1), en(b, 3), en(c, a)], Plan = [mover(b, 3, c), mover(b, c, 3), 148 6 7 8 9 10 11 Planeación mover(c, mover(a, mover(a, mover(b, mover(a, a, 1, b, 3, 1, 2), b), 1), c), b)] Aunque el plan resultante cumple con su cometido, no es precisamente elegante. De hecho, existe un plan de tres movimientos para lograr las metas de este caso! Esto se debe a que el mundo de los bloques es más complejo de lo que parece, debido a la combinatoria. En este problema, el planeador tiene acceso a más opciones entre diferentes acciones que tienen sentido bajo el análisis medios-fines. Más opciones, significa mayor complejidad combinatoria. Regresemos al ejemplo, lo que sucede es que el planeador persigue diferentes metas en diferentes etapas de la construcción del plan. Por ejemplo: mover(b, 3, c) satisfacer en(b, c) mover(b, c, 3) satisfacer clear(c) y ejecutar siguiente acción mover(c, a, 2) satisfacer clear(a) y mover(a, 1, b) mover(a, 1, b) satisfacer on(a, b) mover(a, b, 1) satisfacer clear(b) y mover(b, 3, c) mover(b, 3, c) satisfacer en(b, c) otra vez mover(a, 1, b) satisfacer en(a, b) otra vez Lo que esta tabla muestra es que a veces el planeador destruye metas que ya habı́a satisfecho. El planeador logra fácilmente satisfacer una de las dos metas planteadas, en(b, c) pero la destruye al buscar como satisfacer la otra meta en(a, b). Lo peor es que está forma desorganizada de seleccionar las metas, puede incluso llevar al fracaso en la búsqueda del plan, como en el siguiente ejemplo: 1 2 ?- estado1(E), plan(E,[despejado(2), despejado(3)], Plan, _). ERROR: Out of local stack Hagan un trace de esta corrida, para saber porque la meta falla. Una idea evidente para evitar este comportamiento en nuestro planeador, es mantener una lista de metas protegidas, de forma que las acciones que destruyen estas metas no puedan ser seleccionadas. De forma que el planeador medios-fines con metas protegidas se define como: 1 2 plan_metas_protegidas(EstadoInicial,Metas,Plan,EstadoFinal):plan_mp(EstadoInicial,Metas,[],Plan,EstadoFinal). 3 4 5 plan_mp(Estado,Metas,_,[],Estado) :satisfecho(Estado,Metas). 6 7 8 9 10 plan_mp(Estado,Metas,Protegido,Plan,EstadoFinal) :append( PrePlan, [Accion | PostPlan], Plan), seleccionar( Estado, Metas, Meta), lograr( Accion, Meta), 11.4 Aspectos procedimentales de la búsqueda en amplitud 149 precond( Accion, Condicion), preservar(Accion,Protegido), plan_mp( Estado, Condicion, Protegido, PrePlan, EstadoInter1), aplicar( EstadoInter1, Accion, EstadoInter2), plan_mp( EstadoInter2, Metas, [Meta|Protegido], PostPlan, EstadoFinal). 11 12 13 14 15 16 17 18 19 20 21 22 preservar(Accion,Metas) :borrar(Accion,ListaBorrar), not( (member(Meta,ListaBorrar), member(Meta,Metas))). De forma que si ejecutamos la consulta: 1 2 3 4 5 ?- estado1(E), plan_metas_protegidas(E,[despejado(2), despejado(3)], P, _). E = [despejado(2), despejado(4), despejado(b), despejado(c), en(a, 1), en(b, 3), en(c, a)], P = [mover(b, 3, 2), mover(b, 2, 4)] obtenemos una solución, aunque sigue sin ser la mejor. Un sólo movimiento mover(b, 2, 4) era necesario para cumplir con las metas planeadas. Los planes innecesariamente largos son resultado de la estrategia de búsqueda usada por nuestro planeador. 11.4. Aspectos procedimentales de la búsqueda en amplitud Los planeadores implementados usan esencialmente una estrategia de búsqueda primero en profundidad, pero no por completo. Para poder estudiar lo que está pasando, debemos poner atención al orden en que se generan los planes candidatos. La meta 1 append(PrePlan, [Accion|PostPlan], Plan) es central en este aspecto. La variable Plan no está instanciada cuando esta meta es alcanzada. El predicado append/3 genera al reconsiderar, candidatos alternativos para PrePlan en el siguiente orden: 1 2 3 4 5 PrePlan = []; PrePlan = [_]; PrePlan = [_,_]; PrePlan = [_,_,_]; ... 150 11 Planeación Candidatos cortos para PrePlan son los primeros. PrePlan establece una condición para Accion. Esto permite encontrar una acción cuya condición puede satisfacerse por un plan tan corto como sea posible (simulando búsqueda primero en amplitud). Por otra parte, la lista candidato para PostPlan está totalmente no instanciada, y por tanto su longitud es ilimitada. Por tanto, la estrategia de búsqueda resultante es globalmente primero en profundidad, y localmente primero en amplitud. Con respecto al encadenamiento hacı́a adelante de las acciones que se agregan al plan emergente, se trata de una búsqueda primero en profundidad. Cada acción es validada por un PrePlan, este plan es por otra parte, buscado primero en amplitud. Una forma de minimizar la longitud de los planes es forzar al planeador, en su parte de búsqueda en amplitud, de forma que los planes cortos sean considerados antes que los largos. Podemos imponer esta estrategia embebiendo nuestro planificador en un procedimiento que genere planes candidatos ordenados por tamaño creciente. Por ejemplo: 1 2 3 plan_primero_amplitud(Estado, Metas, Plan, EstadoFinal) :candidato(Plan), plan(Estado,Metas,Plan,EstadoFinal). 4 5 6 candidato([]). 7 8 9 candidato([Primero|Resto]) :candidato(Resto). El mismo efecto puede lograrse de manera más elegante, insertando el generador de planes directamente en el procedimiento plan/4 de forma que: 1 2 3 4 plan_metas_protegidas_amplitud(EstadoInicial,Metas,Plan, EstadoFinal):plan_mp_amplitud(EstadoInicial,Metas,[],Plan, EstadoFinal). 5 6 7 plan_mp_amplitud(Estado,Metas,_,[],Estado) :satisfecho(Estado,Metas). 8 9 10 11 12 13 14 15 16 17 18 19 20 plan_mp_amplitud(Estado,Metas,Protegido,Plan,EstadoFinal) :append(Plan,_,_), append( PrePlan, [Accion | PostPlan], Plan), seleccionar( Estado, Metas, Meta), lograr( Accion, Meta), precond( Accion, Condicion), preservar(Accion,Protegido), plan_mp_amplitud( Estado, Condicion, Protegido, PrePlan, EstadoInter1), aplicar( EstadoInter1, Accion, EstadoInter2), plan_mp_amplitud( EstadoInter2, Metas, [Meta|Protegido], PostPlan, EstadoFinal). 11.4 Aspectos procedimentales de la búsqueda en amplitud 151 Y por tanto podemos volver a computar la meta original, encontrando esta vez el plan más corto: 1 2 3 4 5 6 ?- estado1(E), plan_metas_protegidas_amplitud(E,[despejado(2), despejado(3)], Plan,_). E = [despejado(2), despejado(4), despejado(b), despejado(c), en(a, 1), en(b, 3), en(c, a)], Plan = [mover(b, 3, 4)] Este resultado es óptimo, sin embargo la meta: 1 2 3 4 5 6 7 8 ?- estado1(E), plan_metas_protegidas_amplitud(E,[en(a,b), en(b,c)], Plan, _). E = [despejado(2), despejado(4), despejado(b), despejado(c), en(a, 1), en(b, 3), en(c, a)], Plan = [mover(c, a, 2), mover(b, 3, a), mover(b, a, c), mover(a, 1, b)] sigue siendo problemática. Este resultado se obtiene con y sin protección de metas siguiendo la estrategia primero en amplitud. El segundo movimiento del plan parece superfluo y aparentemente no tiene sentido. Investiguemos porque se le incluye en el plan y porque aún en el caso de la búsqueda primero en amplitud, el plan resultante está lejos del óptimo. Dos preguntas son interesantes en este problema: ¿Qué razones encuentra el planeador para construir este curioso plan? y ¿Por qué el planeador no encuentra el plan óptimo e incluye la acción mover(b, 3, a)? Atendamos la primer pregunta. La última acción mover(a, 1, b) atiende la meta en(a, b). Los tres primeros movimientos están al servicio de cumplir las condiciones de esta acción, en particular la condición despe jado(a). El tercer movimiento despeja a y una condición de este movimiento es en(b, a). Esto se cumple gracias al curioso segundo movimiento mover(b, 3, a). Esto ilustra la clase de exóticos planes que pueden emerger durante un razonamiento medios-fines. Con respecto a la segunda pregunta, ¿Por qué después de mover(c, a, 2), el planeador no considera inmediatamente mover(b, 3, c), lo que conduce a un plan óptimo? La razón es que el planeador estaba trabajando en la meta en(a, b) todo el tiempo. La acción que nos interesa es totalmente superflua para esta meta, y por lo tanto no es considerada. La cuarta acción logra en(a, b) y por pura suerte en(b, c)! Este último resultado no es una decisión planeada de nuestro sistema. De lo anterior se sigue, que el procedimiento medios-fines, tal y como lo hemos implementado es incompleto, no sugiere todas las acciones relevantes para el proceso de planificación. Esto se debe a la localidad con que se computan las soluciones. Solo se sugerirán acciones relevantes para la meta actual del sistema. La solución al problema está en este enunciado: se debe permitir la interacción entre metas en el proceso de planificación. Antes de pasar al siguiente tema, consideren 152 11 Planeación que al introducir la estrategia primero en amplitud para buscar planes más cortos, hemos elevando considerablemente el tiempo de computación necesario para hallar una solución. 11.5. Regresión de metas Supongan que estamos interesados en una lista de metas Metas que se cumplen en cierto estado E. Sea el estado anterior a E, E0 y la acción ejecutada en E0 , A. ¿Qué metas Metas0 tienen que cumplirse en E0 para que Metas se cumpla en E? Metas0 debe tener las siguientes propiedades: 1. La acción A debe ser posible en E0 , por lo que Metas0 debe implicar la condición para A. 2. Para cada meta M en Metas, se cumple que: la acción A agrega M; ó M ∈ Metas0 y A no borra M. El cómputo para determinar Metas0 a partir de Metas y la acción A se conoce como regresión de metas. Por supuesto, sólo estamos interesados en aquellas acciones que agregan alguna meta M a Metas. Las relaciones entre varios conjuntos de metas y condiciones se ilustra en la figura 11.2 Figura 11.2 Relaciones entre conjuntos de condiciones en la regresión de metas vı́a la acción A. El área sombreada representa las metas Metas0 resultado de la regresión. Observen que la intersección entre Metas y la lista borrar de A debe ser vacı́a. El mecanismo de regresión de metas puede usarse como planeador de la siguiente manera. Para satisfacer una lista de metas Metas a partir de un estado EstadoInicial, hacer: Si Metas se cumple en EstadoInicial, entonces el plan vacı́o es suficiente; en cualquier otro caso, seleccionar una meta M ∈ Metas y una acción A que agregue 11.5 Regresión de metas 153 M; entonces computar la regresión de Metas vı́a A obteniendo ası́ NuevasMetas y buscar un plan para satisfacer NuevasMetas desde EstadoInicial. El procedimiento puede mejorarse si observamos que algunas combinaciones de metas son imposibles. Por ejemplo en(a, b) y despe jado(b) no pueden satisfacerse al mismo tiempo. Esto se puede formular vı́a la relación: 1 imposible(Meta,Metas). que indica que la Meta es imposible en combinación con las Metas. Para el caso del mundo de los bloques la incompatibilidad entre las metas se define como: 1 imposible(en(X,X),_). 2 3 4 5 6 7 8 imposible(en(X,Y), Metas) :member(despejado(Y),Metas) ; member(en(X,Y1),Metas), Y1 \== Y ; member(en(X1,Y),Metas) X1 \== X. 9 10 11 imposible(despejado(X),Metas) :member(en(_,X),Metas). El resto del planeador es como sigue: 1 2 plan(Estado, Metas, []) :satisfecho(Estado, Metas). 3 4 5 6 7 8 9 10 11 plan(Estado, Metas, Plan) :append( PrePlan, [Accion], Plan), seleccionar( Estado, Metas, Meta), lograr(Accion, Meta), precond(Accion, Condicion), preservar(Accion, Metas), regresion(Metas, Accion, MetasReg), plan(Estado, MetasReg, PrePlan). 12 13 14 satisfecho(Estado, Metas) :borrar_todos(Metas,Estado,[]). 15 16 17 seleccionar(_, Metas, Meta) :member( Meta, Metas). 18 19 20 21 lograr( Accion, Meta) :agregar( Accion, Metas), member( Meta, Metas). 22 23 borrar_todos( [], _, []). 24 25 26 borrar_todos( [X | L1], L2, Diff) member( X, L2), !, :- 154 11 Planeación borrar_todos( L1, L2, Diff). 27 28 29 30 borrar_todos( [X | L1], L2, [X | Diff]) borrar_todos( L1, L2, Diff). :- 31 32 33 34 35 preservar(Accion,Metas) :borrar(Accion,ListaBorrar), not( (member(Meta,ListaBorrar), member(Meta,Metas))). 36 37 38 39 40 41 regresion(Metas, Accion, MetasReg) :agregar(Accion, NuevasRels), borrar_todos(Metas, NuevasRels, RestoMetas), precond(Accion, Condicion), agregarNuevo(Condicion,RestoMetas,MetasReg). 42 43 agregarNuevo([],L,L). 44 45 46 47 48 agregarNuevo([Meta|_],Metas,_) :imposible(Meta,Metas), !, fail. 49 50 51 52 agregarNuevo([X|L1],L2,L3) :member(X,L2), !, agregarNuevo(L1,L2,L3). 53 54 55 agregarNuevo([X|L1],L2,[X|L3]) :agregarNuevo(L1,L2,L3). Ahora es posible encontrar el plan óptimo de tres movimientos para el problema del mundo de los bloques: 1 2 3 4 ?- estado1(E), plan(E,[en(a,b),en(b,c)],P). E = [despejado(2), despejado(4), despejado(b), despejado(c), en(a, 1), en(b, 3), en(c, a)], P = [mover(c, a, 2), mover(b, 3, c), mover(a, 1, b)] 11.6. Combinando planeación medios fines con primero el mejor Los planeadores construidos hasta ahora hacen uso de estrategias de búsqueda básicas: primero en profundidad, o primero en amplitud, o una combinación de ambas. Estas estrategias son totalmente desinformadas, en el sentido que no pueden usar información del dominio del problema para guiar su selección entre alternativas posibles. En consecuencia, estos planeadores son sumamente ineficientes, salvo en casos muy especiales. Existen diversas maneras de introducir una guı́a heurı́stica, 11.6 Combinando planeación medios fines con primero el mejor 155 basada en el dominio del problema, en nuestros planeadores. Algunos lugares donde esto puede hacerse son: En la relación seleccionar(Estado, Metas, Meta) que decide el orden en que las metas serán procesadas. Por ejemplo, una guı́a en el mundo de los bloques es que las torres deben estar bien cimentadas, de forma que la relación en/2 más arriba de la torre, deberı́a resolverse al último (o primero en el planeador por regresión, que soluciona el plan en orden inverso). Otra guı́a es que las metas que ya se cumplen en el medio ambiente, deberı́an postergarse. En la relación lograr(Accion, Meta) que decide que acción alternativa será intentada para lograr una meta dada. Observen que nuestro planeador también genera alternativas al procesar precond/2. Por ejemplo, algunas acciones son “mejores” porque satisfacen más de una meta simultáneamente. También, con base en la experiencia, podemos saber que cierta condición es más fácil de satisfacer que otras. Decisiones acerca de qué conjunto de regresión de metas debe considerarse a continuación. Por ejemplo, seguir trabajando en el que parezca más fácil de resolver, buscando ası́ el plan más corto. Esta última idea muestra como podemos imponer una estrategia primero el mejor en nuestro planeador. Esto implica computar un estimado heurı́stico de la dificultad de conjuntos de regresión de metas alternativos, para expandir el más promisorio. Recuerden que para usar este tipo de estrategia es necesario especificar: 1. 2. 3. 4. Una relación s/3 entre nodos del espacio de búsqueda: s(Nodo1 , Nodo2 ,Costo). Los nodos meta en el espacio: meta(Nodo). Una función heurı́stica de la forma h(Nodo, Hestimado). El nodo inicial de la búsqueda. Una forma de definir estos requisitos es asumir que los conjuntos de regresión de metas son nodos en el espacio de búsqueda. Esto es, en el espacio de búsqueda hará una liga entre Metas1 y Metas2 si existe una acción A tal que: 1. A agrega alguna meta ∈ Metas1 . 2. A no destruye ninguna meta ∈ Metas1 3. Metas2 es el resultado de la regresión de Metas1 a través de A, tal y como definimos en nuestro planeador anterior: regresion(Metas1 , A, Metas2 ). Por simplicidad, asumiremos que todas las acciones tienen el mismo costo, y en consecuencia asignaremos Costo = 1 en todas las ligas del espacio de búsqueda. Por lo que la relación s/3 se define como: 1 2 3 4 5 6 s(Metas1,Metas2) :member(Meta,Metas1), lograr(Accion,Meta), precond(Accion,Cond), preservar(Accion,Metas1), regresion(Metas1,Accion,Metas2). 156 11 Planeación Cualquier conjunto de metas que sea verdadero en la situación inicial de un plan, es un nodo meta en el espacio de búsqueda. El nodo inicial de la búsqueda es la lista de metas que el plan debe lograr. Aunque la representación anterior tiene todos los elementos requeridos, tiene un pequeño defecto. Esto se debe a que nuestra búsqueda primero el mejor encuentra un camino solución como una secuencia de estados y no incluye acciones entre los estados. Por ejemplo, la secuencia de estados (listas de metas) para logra en(a, b) en el estado inicial que hemos estado usando es: 1 2 3 [ [despejado(c), despejado(2), en(c,a), despejado(b), en(a,1)] [despejado(a), despejado(b), en(a,1)] [en(a,b)] ] El primer estado es verdadero por la situación inicial, el segundo es resultado de la acción mover(c, a, 2) y el tercero es resultado de la acción mover(a, 1, b). Observen que la búsqueda primero el mejor regresa el camino solución en el orden inverso. En nuestro caso es una ventaja, porque los planes son construidos en la regresión hacı́a atrás, ası́ que al final obtendremos la secuencia de acciones en el orden correcto. Sin embargo, es raro no tener mención explı́cita a las acciones en el plan, aunque puedan reconstruirse de las diferencias entre listas de metas. Podemos incluir las acciones en el camino solución fácilmente, basta con agregar a cada estado la acción que se sigue de él. De forma que los nodos del espacio de búsqueda tendrán la forma: 1 Metas -> Acción Su implementación detallada es la siguiente: 1 :- op(300,xfy, ->). 2 3 4 5 6 7 8 s(Metas -> AccSiguiente,MetasNuevas -> Accion, 1) :member(Meta,Metas), lograr(Accion,Meta), precond(Accion,Cond), preservar(Accion,Metas), regresion(Metas,Accion,MetasNuevas). 9 10 11 12 meta(Metas -> Accion) :inicio(Estado), satisfecho(Estado,Metas). 13 14 15 16 17 h(Metas -> Accion,H) :inicio(Estado), borrar_todos(Metas,Estado,Insatisfecho), length(Instatisfecho,H). 18 19 20 inicio([en(a,1),en(b,3),en(c,a),despejado(b),despejado(c), despejado(2),despejado(4)]). 11.6 Combinando planeación medios fines con primero el mejor 157 Ahora podemos usar nuestro viejo buscador primero el mejor: 1 2 primeroMejor(Inicio,Solucion) :expandir([],hoja(Inicio,0/0),9999,_,si,Solucion). 3 4 5 6 7 8 9 %% % expandir(Camino,Arbol,Umbral,Arbol1,Solucionado,Solucion) %% % Camino es el recorrido entre Inicio y el nodo en Arbol %% % Arbol1 es Arbol expandido bajo el Umbral %% % Si la meta se encuentra, Solucion guarda el camino solución %% % y Solucionado = si 10 11 % Caso 1: la hoja con Nodo es una meta, construye una solución 12 13 14 expandir(Camino,hoja(Nodo,_),_,_,si,[Nodo|Camino]) :meta(Nodo). 15 16 17 % % Caso 2: una hoja con f-valor menor o igual al Umbral Generar succesores de Nodo y expandirlos bajo el Umbral 18 19 20 21 22 23 24 25 26 27 28 29 expandir(Camino,hoja(Nodo,F/G),Umbral,Arbol1,Solucionado,Sol) :F =< Umbral, (bagof( M/C,(s(Nodo,M,C),not(member(M,Camino))),Succ), !, % Nodo tiene sucesores listaSuccs(G,Succ,As), % Encontras subárboles As mejorF(As,F1), % f-value of best successor expandir(Camino,arbol(Nodo,F1/G,As),Umbral,Arbol1, Solucionado,Sol) ; Solucionado = nunca % Nodo no tiene sucesores ). 30 31 32 33 % % % Caso 3: Nodo interno con f-valor menor al Umbral Expandir el subárbol más promisorio con cuyo resultado, continuar/7 decidirá como proceder 34 35 36 37 38 39 40 41 expandir(Camino,arbol(Nodo,F/G,[A|As]),Umbral,Arbol1, Solucionado,Sol) :F =< Umbral, mejorF(As,MejorF), min(Umbral,MejorF,Umbral1), expandir([Nodo|Camino],A,Umbral1,A1,Solucionado1,Sol), continuar(Camino,arbol(Nodo,F/G,[A1|As]),Umbral,Arbol1, Solucionado1,Solucionado,Sol). 42 43 44 % % Caso 4: Nodo interno con subárboles vacı́o Punto muerto, el problema nunca será resuelto 45 46 expandir(_,arbol(_,_,[]),_,_,nunca,_) :- !. 47 48 49 50 % % Caso 5: f-valor mayor que el Umbral Arbol no debe crecer 158 51 52 11 Planeación expandir(_,Arbol,Umbral,Arbol,no,_) f(Arbol,F), F > Umbral. :- 53 54 55 %% % continuar(Camino,Arbol,Umbral,NuevoArbol,SubarbolSolucionado, %% % ArbolSolucionado,Solucion) 56 57 58 % Caso 1: el subartol y el arbol están solucionados % la solución está en Sol 59 60 continuar(_,_,_,_,si,si,Sol). 61 62 63 64 65 66 67 continuar(Camino,arbol(Nodo,F/G,[A1|As]),Umbral,Arbol1,no, Solucionado,Sol) :insertarArbol(A1,As,NAs), mejorF(NAs,F1), expandir(Camino,arbol(Nodo,F1/G,NAs),Umbral,Arbol1, Solucionado,Sol). 68 69 70 71 72 73 continuar(Camino,arbol(Nodo,F/G,[_|As]),Umbral,Arbol1,nunca, Solucionado,Sol) :mejorF(As,F1), expandir(Camino,arbol(Nodo,F1/G,As),Umbral,Arbol1, Solucionado,Sol). 74 75 76 %% % listaSuccs(G0,[Nodo1/Costo1, ...], [hoja(MejorNodo,MejorF/G), ...]) %% % hace una lista de árboles sucesores ordendados por F-valor 77 78 listaSuccs(_,[],[]). 79 80 81 82 83 84 85 listaSuccs(G0,[Nodo/C|NCs],As) :G is G0 + C, h(Nodo,H), % Heuristic term h(N) F is G + H, listaSuccs(G0,NCs,As1), insertarArbol(hoja(Nodo,F/G),As1,As). 86 87 %% % Inserta A en una lista de arboles As preservando el orden por f-valor 88 89 90 91 insertarArbol(A,As,[A|As]) :f(A,F), mejorF(As,F1), F =< F1, !. 92 93 94 insertarArbol(A,[A1|As], [A1|As1]) insertarArbol(A,As,As1). :- 95 96 97 %% % Extraer f-valores 98 99 100 f(hoja(_,F/_),F). f(arbol(_,F/_,_),F). 101 102 mejorF([A|_],F) :- f( A, F). % f-valor de una hoja % f-valor de un árbol 11.7 Variables y planes no lineales 103 159 mejorF([], 9999). 104 105 106 min(X,Y,X) :- X min(_,Y,Y). =< Y, !. De forma que podemos procesar el plan con la siguiente llamada: 1 2 3 4 5 6 7 8 ?- primeroMejor([en(a,b), en(b,c)] -> stop, Plan). Plan = [[despejado(2), en(c, a), despejado(c), en(b, 3), despejado(b), en(a, 1)]->mover(c, a, 2), [despejado(c), en(b, 3), despejado(a), despejado(b), en(a, 1)]->mover(b, 3, c), [despejado(a), despejado(b), en(a, 1), en(b, c)] ->mover(a, 1, b), [en(a, b), en(b, c)]->stop] La acción nula stop es necesaria pues todos los nodos deben incluir una acción. Aunque la heurı́stica usada es simple, el programa debe ser más rápido que las versiones anteriores. Eso si, el precio a pagar es una mayor utilización de la memoria, debido a que debemos mantener el conjunto de alternativas competitivas. 11.7. Variables y planes no lineales A manera de comentario final, consideraremos dos casos que pueden mejorar la eficiencia de los planificadores construidos en esta sesión. El primer caso consiste en permitir que las acciones y las metas contengan variables no instanciadas; el segundo caso es considerar que los planes no son lineales. 11.7.1. Acciones y metas no instanciadas Las variables que ocurren en nuestros planeadores están siempre instanciadas. Esto se logra, por ejemplo en la relación precond/2 cuyo cuerpo incluye la meta block(Block) entre otras. Este tipo de meta hace que Block siempre esté instanciada. Esto puede llevar a la generación de numerosos movimientos alternativos irrelevantes. Por ejemplo, cuando al planeador se le plantea como meta despe jar(a), éste utiliza lograr/2 para generar movimientos que satisfagan despe jado(a): 1 mover(De,a,A) Entonces se computan las condiciones necesarias para ejecutar esta acción: 1 precond(mover(De,a,A)),Cond) 160 11 Planeación Lo cual fuerza, al reconsiderar, varias instanciaciones alternativas para De y A: 1 2 3 4 5 6 7 mover(b,a,1) mover(b,a,2) mover(b,a,3) mover(b,a,4) mover(b,a,c) mover(b,a,1) mover(b,a,2) Para hacer más eficiente este paso del planeador, es posible permitir variables no instanciadas en las metas. Para el ejemplo del mundo de los bloques, las condiciones de mover serı́an definidas como: 1 2 precond(mover(Bloque,De,A), [despejado(Bloque),despejado(A),en(Bloque,De)]). Si reconsideramos con esta nueva definición la situación inicial, la lista de condiciones computadas serı́a: 1 [despejado(Bloque),despejado(A),en(Bloque,a)] Observen que esta lista de metas puede ser satisfecha inmediatamente en la situación inicial de nuestro ejemplo si Bloque/c y A/2. Esta mejora en eficiencia se logra postergando la decisión de cómo instanciar las variables, al momento en que ya se cuenta con más información para ello. Este ejemplo ilustra el poder de la representación con variables, pero el precio a pagar es una mayor complejidad. Para empezar, nuestro intento por definir precond para mover/3 es erróneo, pues permite movimientos como mover(c, a, c), que da como resultado que !el bloque c esté en el bloque c! Esto podrı́a arreglarse si especificáramos que De y A deben ser diferentes: 1 2 3 4 precond(mover(Bloque,De,A), [despejado(Bloque),despejado(A),en(Bloque,De), diferente(Bloque,A), diferente(De,A), diferente(Bloque,De)]). donde di f erente/2 significa que los dos argumentos no denotan al mismo objeto Prolog. Una condición como estas, no depende del estado del problema, de forma que no puede volverse verdadero mediante acción alguna, pero debe verificarse evaluando el predicado correspondiente. Una manera de manejar estas cuasi-metas es agregar al predicado satis f echo/2 la siguiente cláusula: 1 2 3 satisfecho(Estado,[Meta|Metas]) :satisface(Meta), satisfecho(Estado,Metas). 11.7 Variables y planes no lineales 161 De forma que debemos definir también: 1 satisface(diferente(X,Y)) Tal relación tiene éxito si X y Y no se corresponden. Si X y Y son lo mismo, la condición es falsa. Este caso deberı́a tratarse con imposible, pues la condición deberá seguir siendo falsa, sin importar las acciones que serán adoptadas en el plan. En otro caso, estamos ante falta de información y satis f ace se deberı́a postergar. 11.7.2. Planes no lineales Un problema con nuestro planeador es que considera todos los posibles ordenes de las acciones, aún cuando las acciones son completamente independientes. Consideren el problema ilustrado en la figura 11.3, donde la meta es construir dos pilas de bloques que están de antemano bien separados. Las dos pilas puede construirse independientemente con los siguientes planes: 1 2 Plan1 = [mover(b,a,c), mover(a,1,b)] Plan2 = [mover(e,d,f), mover(d,8,e)] El punto importante aquı́ es que estos planes no interaccionan entre ellos, de forma que el orden de las acciones sólo es relevante dentro de cada plan. Tampoco importa si se ejecuta primero Plan1 o Plan2 y es incluso posible ejecutarlos de manera alternada, por ejemplo: 1 [mover(b,a,c), mover(e,d,f), mover(d,8,e), mover(a,1,b)] Sin embargo, nuestro planeador considerará las 24 permutaciones posibles de las cuatro acciones, aunque existan solo 4 alternativas: 2 permutaciones para cada uno de los planes. El problema se debe a que el planeador insiste en el orden total de las acciones en el plan. Una mejora se lograrı́a si, en los casos donde el orden no es importante, la precedencia entre las acciones se mantiene indefinida. Entonces nuestros planes serán conjuntos de acciones parcialmente ordenadas. Los planeadores que aceptan este tipo de representación se conocen como planeadores no lineales. Consideremos nuevamente el ejemplo de la figura 11.3. Analizando las metas en(a, b) y en(b, c) el planeador no lineal concluye que las siguientes dos acciones son necesarias en el plan: 1 2 M1 = mover(a,X,b) M2 = mover(b,Y,c) 162 11 Planeación e b a c 1 2 1 3 4 5 6 f d 7 8 a d b e c f 2 3 4 5 6 7 8 Figura 11.3 Una tarea de planeación con dos planes independientes No hay otra forma de resolver ambas metas, pero el orden de estas acciones es aún indeterminado. Ahora consideren las condiciones de ambas acciones. La condición de mover(a, X, b) incluye depe jado(a), la cual no se satisface en la situación inicial, por lo que necesitamos una acción de la forma: 1 M3 = mover(Bloque,a,A). que precede a M1. Ahora tenemos una restricción en el orden de las acciones: 1 antes(M3,M1) Ahora revisamos si M3 y M1 pueden ser el mismo movimiento. Como este no es el caso, el plan tendrá que incluir tres movimientos. Ahora el planeador debe preguntarse si hay una permutación de [M1, M2, M3] tal que M3 preceda a M1, tal que la permutación es ejecutable en el estado inicial del problema y las metas se cumplen en el estado resultante. Dadas las restricciones de orden anteriores tres permutaciones de seis, cumplen con los requisitos: 1 2 3 [M3,M1,M2] [M3,M2,M1] [M2,M3,M1] Y de estas permutaciones, solo la del medio cumple con el requisito de ser ejecutable bajo la sustitución Bloque/c, A/2, X/1,Y /3. Como se puede intuir, la complejidad computacional no puede ser evitada del todo por un planeador no lineal, pero puede ser aliviada considerablemente. Referencias 163 Referencias 1. I. Bratko. Prolog programming for Artificial Intelligence. Addison-Wesley, 3rd edition, 2001. 2. Alonzo Church. A note on the entscheidungsproblem. Journal of Symbolic Logic, 1:40–41, 1936. 3. K. Clark. Negations as failure. In H. Gallaire and J. Minker, editors, Logic and Databases, pages 293–322. Plenum Press, New York, USA, 1978. 4. A. Colmerauer and P. Roussel. The birth of Prolog. In T. H. Bergin and R. G. Gibson, editors, History of Programming Languages, chapter The birth of Prolog, pages 331–367. ACM Press / Addison-Wesley, 1996. 5. M.R. Genesereth and N.J. Nilsson. Logical Foundations for Artificial Intelligence. Morgan Kauffman Publishers, Inc., Palo Alto, CA., USA, 1987. 6. R. A. Kowalski. Predicate logic as a programming language. In J. L. Rosenfeld, editor, Information Processing, pages 569–574. North-Holland, 1974. 7. Robert A. Kowalski and Donald Kuehner. Linear resolution with selection function. Artificial Intelligence, 2(3/4):227–260, 1971. 8. J. McCarthy. Programs with common sense. In Proceedings of the Symposium on the Mechanization of Thought Processes, Teddington, England, 1958. 9. M. Minsky. The Society of Mind. Simon and Schuster, New York, NJ., USA, 1986. 10. T.M. Mitchell. Machine Learning. Computer Science Series. McGraw-Hill International Editions, Singapore, 1997. 11. Shan-Hwei Nenhuys-Chen and Ronald de Wolf. Foundations of Inductive Logic Programming, volume 1228 of Lecture Notes in Artificial Intelligence. Springer-Verlag, Berlin Heidelberg, 1997. 12. Ulf Nilsson and Jan Maluszynski. Logic, Programming and Prolog. John Wiley & Sons Ltd, 2nd edition, 2000. 13. J. R. Quinlan. Induction of decision trees. Machine Learning, 1:81–106, 1986. 14. J.R. Quinlan. C4.5: Programs for Machine Learning. Morgan Kaufmann, San Mateo, CA., USA, 1993. 15. J. A. Robinson. A machine-oriented logic based on the resolution principle. Journal of the ACM, 12(1):23–41, 1965. 16. J. A. Robinson. Logic and logic programming. Communications of the ACM, 35(3):40–65, 1992. 17. Stuart J. Russell and Peter Norvig. Artificial Intelligence, a modern approach. Prentice Hall, New Jersey, USA, 2nd edition, 2003. 18. C. Shannon and W. Weaver. The mathematical theory of communication. The Bell System Technical Journal, 27:623–656, July, October 1948. 19. Alan M. Turing. On the computable numbers, with applications to the entscheidungsproblem. In Proceedints of the London Mathematical Society, volume 42 of series 2, pages 230–265, 1936. 20. D. H. D. Warren. An abstract Prolog instruction set. Technical Report 309, SRI, 1983.