Programas mutantes: manipulación de la base de conocimiento 1 / 30 Programas mutantes Una de las razones por las que Prolog está tan ligado a la inteligencia artificial es por su capacidad para permitir que los programas se modifiquen a sı́ mismos. Un programa es un conjunto de reglas que representan conocimiento... si el programa puede modificarse a sı́ mismo, entonces puede aprender (?) Muchas de las expectativas en el ámbito de la inteligencia artificial de los años 70 han quedado relegadas al olvido... inteligencia, aprendizaje, son términos complejos (y hasta polémicos). Pero Prolog sigue joven y en activo!! Hacer que un programa se auto-modifique sigue teniendo aplicaciones algorı́tmicas interesantes, versátiles y muy útiles. 2 / 30 Acceso al programa desde el programa Un programa Prolog es un conjunto de cláusulas (hechos, reglas). Podrı́amos concebir predicados para introducir una nueva cláusula en el programa o eliminar una cláusula existente predicados para la modificación dinámica del programa. Prolog proporciona esos predicados: assert, retract. Son predicados metalógicos (igual que el corte o la negación, su semántica es extra-lógica), pero son sencillos de utilizar... y muy útiles! Nota: estos predicados pueden utilizarse para simular variables globales y asignación. En general es una mala práctica en programación lógica y conduce a programas mal diseñados, difı́ciles de entender y de mantener. 3 / 30 Manipulación de la base de conocimiento (programa) El predicado listing muestra las cláusulas de programa (las definidas por el usuario). Si arrancamos el intérprete y resolvemos: ?- listing. ... Yes ?- Es decir, la base de conocimiento está vacı́a (no hay ninguna regla de programa). Si ahora hacemos (desde el prompt de Prolog): ?- assert(guapa(sara)). habremos introducido la cláusula (hecho): guapa(sara). 4 / 30 Manipulación del programa II Ahora: ?- listing. ... guapa(sara). Introducimos más hechos: ?- assert(guapa(laura)), assert(guapa(ana)), assert(guapa(maria)), guapa(sara). Ahora: ?- listing. ... guapa(sara). guapa(laura). guapa(ana). guapa(maria). guapa(sara). (Ojo: sara es guapa “dos veces”, porque se ha asertado dos veces ese hecho) 5 / 30 Manipulación del programa III Hasta ahora hemos introducido solo hechos, pero también podemos asertar reglas: ?- assert((lista(X):-guapa(X))). X = G180 Yes ?- listing. guapa(sara). guapa(laura). guapa(ana). guapa(maria). guapa(sara). lista(A) :- guapa(A). 6 / 30 Manipulación del programa IV Este proceso serı́a equivalente a escribir en un archivo los hechos y la regla anteriores y haber hecho la consulta de dicho archivo. Podemos también eliminar cláusulas: ?- retract(guapa(sara)), retract(guapa(ana)). ?- listing. guapa(laura). guapa(maria). guapa(sara). lista(A) :- guapa(A). Se ha eliminado el primer hecho “guapa(sara)” y “guapa(ana)”. Para eliminar todos los hechos “guapa(...)” harı́amos: ?- retract(guapa(X)). ?- listing. lista(A) :- guapa(A). 7 / 30 Más control sobre los asserts y retracts asserta(+C)::= aserta la cláusula C al principio de la base de conocimiento assertz(+C)::= aserta la cláusula C al final de la base de conocimiento retractall(+Head)::= elimina todas las cláusulas cuya cabeza unifique con Head record[a,z,ed](. . . ), abolish(. . . ): consultar manual de Prolog. 8 / 30 Memoization or caching: almacenando conocimiento. Supongamos que definimos el siguiente predicado: tabla(L) :member(X,L), member(Y,L), V is X*Y, assert(mult(X,Y,V)), fail. Y supongamos que resolvemos el siguiente objetivo: ?- tabla([1,2,3,4,5,6,7,8,9]). Qué efecto tiene? toma dos elementos X e Y de la lista [1,2,3,4,5,6,7,8,9] calcula su producto X*Y en V aserta un hecho mult(X,Y,V) falla... y por backtraking vuelve a tomar otros dos elementos, etc 9 / 30 Memoization or caching II El objetivo falla, pero como efecto lateral obtenemos una colección de hechos mult(X,Y,V) que contienen la tabla de multiplicar que aprendiamos en el cole. Para qué puede ser esto útil? Guardar la tabla de multiplicar no ofrece muchas ventajas: cuesta poco calcular el producto de dos números. Pero esta misma técnica puede ser útil para almacenar cómputos más complejos y evitar reevaluaciones. 10 / 30 Fibonacci revisitado :- dynamic tab fib/2. tab fib(0,1). tab fib(1,1). fibTabulado(N,F):- tab fib(N,F), !. fibTabulado(N,F):- N1 is N-1, N2 is N-2, fibTabulado(N1,F1), fibTabulado(N2,F2), F is F1+F2, assert(tab fib(N,F)). La declaración :- dynamic tab fib/2. indica que el predicado tab fib de aridad 2 es dinámico, i.e., es susceptible de cambiar durante la ejecución (por medio de assert/retract). Los hechos tab fib(0,1). y tab fib(1,1). tabular el valor de los dos primeros términos de la serie. El predicado fibTabulado(N,F) calcula el valor del N-ésimo término de la serie utilizando tabulación o caching o memoization, es decir, utilizando la tabla. 11 / 30 Fibonacci revisitado (II) Veamos en detalle: fibTabulado(N,F):- tab fib(N,F), !. fibTabulado(N,F):- N1 is N-1, N2 is N-2, fibTabulado(N1,F1), fibTabulado(N2,F2), F is F1+F2, assert(tab fib(N,F)). Para resolver fibTabulado(N,F): la primera cláusula comprueba si ese valor está tabulado. Si es ası́, devuelve ese valor de la tabla y corta la búsqueda (no hay más que calcular). Si ese valor aún no esta tabulado (segunda cláusula): Lo calcula en F, que es el valor de devolución pero además lo aserta como un nuevo hecho tab fib(N,F), de modo que si vuelve a necesitarse ese valor no se calculará de nuevo, sino que se obtendrá de la base de conocimiento mediante la primera cláusula. 12 / 30