Chapter 1 Lenguajes de programación para resolver problemas Objetivo El alumno podrá instrumentar programas de Inteligencia Artificial Figure 1.1: Pregunta incómoda Un lenguaje de programación es un lenguaje artificial diseñado para expresar instrucciones que pueden ser llevadas a cabo por máquinas como las computadoras. De acuerdo al nivel de abstracción, se clasifican en lenguajes de bajo nivel y lenguajes de alto nivel. Un programa es un conjunto de instrucciones que una vez ejecutadas realizarán una o varias tareas en una computadora. 1.1 Estructuras básicas De acuerdo al paradigma de programación que utilizan, los lenguajes se pueden clasificar como imperativos o declarativos. ♦ La programación imperativa describe la programación en términos del estado del programa y sentencias que cambian dicho estado. Los programas imperativos son un conjunto 1 CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 2 de instrucciones que le indican a la computadora cómo realizar una tarea. ♦ La programación declarativa es un paradigma de programación que está basado en el desarrollo de programas especificando o "declarando" un conjunto de condiciones, proposiciones, afirmaciones, restricciones, ecuaciones o transformaciones que describen el problema y detallan su solución. La solución es obtenida mediante mecanismos internos de control, sin especificar exactamente cómo encontrarla, tan sólo se le indica a la computadora qué es lo que se desea obtener o qué es lo que se está buscando. Lenguajes de Programación Estructurados C Pascal COBOL php ... Imperativos Declarativos Orientados a Objetos Funcionales Python C++ Java C# php5 ... Lógicos Prolog CLIPS ... Haskell LISP Dr. Scheme Dr. Racket ... Figure 1.2: Clasificación de lenguajes de acuerdo al paradigma Un programa (algoritmo) contiene instrucciones elementales seleccionadas cuidadosamente que pueden ser realizadas por un robot ó un procesador; el procesador recibe las órdenes y ejecuta lo que indican, resulta obvio que la disposición de las instrucciones resulta crucial al momento de llevarlas a cabo. Por tanto, el algoritmo debe incluir instrucciones de control que modifiquen la ruta que debe seguir el procesador, además de indicar que debe hacerse en cada paso, así como el momento en el cual debe detenerse. Se ha probado que para implementar cualquier algoritmo, son suficientes tres construcciones básicas para control de flujo: ♦ Secuencia. ♦ Condicional. ♦ Ciclos iterativos (repetitivos). A continuación se describe cada una de ellas y se muestra su representación en pseudocódigo. CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 3 Ejecución secuencial La ejecución secuencial consiste en ejecutar una instrucción y al terminar, realizar lo que indica el siguiente paso del algoritmo. . . . n. Instrucción i o. Instrucción i+1 . . . Figure 1.3: Ejecución secuencial Ejecución condicional Consiste en ejecutar un grupo de acciones A ó un grupo de acciones B (pero no ambos) en función del resultado de la evaluación de una condición C (V erdadero ó F also). . . . n. SI c ENTONCES n.1. Subinstrucción 1 . . . n.k. Subinstrucción k o. SI NO o.1 Subinstrucción 1 . . . o.j. Subinstrucción j . . . Figure 1.4: Ejecución condicional Un algoritmo que sólo contiene las estructuras de control anteriores funciona para tareas de longitud fija, dado que cada instrucción se ejecuta solamente una vez. Sin embargo, generalmente se requiere que algunas instrucciones sean ejecutadas más de una vez, para esto, existen estructuras de control que se encargan de la ejecución repetida de bloques de instrucciones, se conocen de forma genérica como estructuras iterativas o ciclos. Iteración definida Sirve para ejecutar un grupo de instrucciones A exactamente N veces, donde N es un entero positivo. CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 4 . . . n. DESDE cont←1 HASTA N n.1. Subinstrucción 1 . . . n.k. Subinstrucción k n.k+1 cont←cont+1 . . . Figure 1.5: Iteración definida Iteración condicional. Conocida también como iteración indefinida, se utiliza para repetir un bloque de acciones A, mientras una condición C sea verdadera, C es una condición de paro. . . . n. MIENTRAS c HACER n.1. Subinstrucción 1 . . . n.k. Subinstrucción k . . . Figure 1.6: Iteración condicional 1.2 Estructuras compuestas Actualmente existen lenguajes de programación (tanto imperativos como declarativos) más expresivos, entre otras cosas incluyen estructuras de datos compuestas (conocidas como tipos abstractos de datos ADT ) entre sus tipos básicos. Esto permite desarrollar aplicaciones en tiempos mucho más cortos. 1.2.1 Listas Una lista (list) consiste de una secuencia de nodos, en los que se almacenan campos de datos arbitrarios y una o dos referencias (apuntadores) al nodo anterior o posterior. La ventaja principal de las listas ligadas respecto a los arreglos es que el tamaño de la misma puede crecer y disminuir en tiempo de ejecución, es decir, no es necesario conocer el tamaño máximo de la lista desde el principio. 1.2.1.1 Lista ligada Una lista ligada (linked list) es un tipo de dato recursivo que es una lista vacía (nulo, nil o null ) o una referencia a un nodo compuesto de un dato y una referencia a una lista ligada. Figure 1.7: Lista ligada CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 5 Las operaciones y la complejidad temporal asintótica de las mismas se muestran en la siguiente tabla: Operación crearListaCircular () esV acia (l) insertar (l, x) buscar (l, x) sacar (l, x) 1.2.1.2 Orden O (1) O (1) O (1) O (n) O (n) Lista doblemente ligada Una lista doblemente ligada (doubly linked list) consiste de una secuencia de nodos, en los que se almacenan campos de datos arbitrarios y dos referencias (apuntadores) al nodo anterior y siguiente. Figure 1.8: Lista doblemente ligada La complejidad temporal asintótica de las operaciones es: Operación crearListaDobleLigada () esV acia (l) insertar (l, x) buscar (l, x) sacar (l, x) Orden O (1) O (1) O (1) O (n) O (n) La lista es un ADT muy importante, ya que tomando como base una listas se pueden implementar otros ADT (como pilas o colas) sin la restricción de tener un número máximo de elementos a almacenar. 1.2.2 Tablas de símbolos Un caso particular de los métodos de hash, son las llamadas tablas de símbolos. Su objetivo principal es asociar un valor con una llave (comúnmente una cadena). Se pueden insertar parejas clave-valor esperando que posteriormente se puede recuperar el valor asociado a una llave dada. Estas operaciones son tan importantes en muchas aplicaciones computacionales que las tablas de símbolos se encuentran disponibles en muchos lenguajes de programación, por ejemplo: ♦ En php se dispone de arreglos asociativos. ♦ Python provee el tipo básico diccionario. ♦ En java se utiliza la clase Hashtable, etc. CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 6 Figure 1.9: Diccionario en Python La definición del ADT tabla de símbolos es la siguiente: Una tabla de símbolos es una estructura de datos que almacena parejas llave-valor y que soporta las operaciones de: ♦ Insertar una nueva pareja llave-valor. ♦ Buscar el valor asociado a una llave. ♦ Eliminar una pareja llave-valor. Idealmente, una tabla de símbolos realiza las operaciones de insertar y buscar sean O (1) en el peor caso. Sin embargo, esto sólo sucede si se tiene conocimiento a priori, es decir, se sabe de antemano qué elementos se almacenarán; desafortunadamente, en general no se tiene esta información. Por lo tanto, solo se garantiza obtener O (1) para el caso promedio. 1.2.3 Árboles Un árbol es una gráfica acíclica dirigida totalmente conexa en la que cada nodo tiene cero o más nodos hijos y a lo más un nodo padre. Solamente puede existir un nodo que no tiene padre, llamado nodo raíz , los nodos que no tienen hijos se llaman nodos hoja y los nodos intermedios (que tiene padre e hijos) se llaman nodos rama o subárboles. Figure 1.10: Un árbol 1.2.3.1 Representación de árboles en la computadora Una manera de representar un árbol es la siguiente. Nodo : dato : e n t e r o #data CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 7 h i j o s : l i s t a D e N o d o s #h o j a => l i s t a v a c í a Arbol : Arbol : Nodo Esta definición tiene un problema en la representación de lista de nodos hijos, si se hace mediante un arreglo, se tiene el problema de calcular el máximo número de hijos posibles, si se utiliza una lista, la estructura completa se puede volver muy compleja, dado que incluirá la estructura del mismo árbol dentro de la estructura de una lista ligada. Figure 1.11: Un árbol que utiliza la representación de lista de hijos Una forma alternativa de representar un árbol es la siguiente. Nodo : dato : e n t e r o h i j o : Nodo hermano : Nodo #data #h o j a => h i j o=n u l o Arbol : Arbol : Nodo En esta forma de representar árboles, cada nodo apunta a su nodo hermano siguiente, de forma tal que el problema de representar la lista de todos los hijos desaparece. Figure 1.12: Un árbol que utiliza la representación de nodos hermanos Otro atractivo de esta representación es que se obtiene una estructura muy conocida: árbol binario. CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 8 Inducción estructural La inducción estructural es un método de demostración utilizado en lógica matemática, computación y en otras áreas. Se trata de una generalización de la inducción matemática. Una prueba por inducción estructural consiste en demostrar que una proposición se cumple para todos los elementos mínimos del tipo (caso base), y si la propiedad se cumple para todas las subestructuras de una cierta estructura S, entonces se debe cumplir también para S. Listas Por ejemplo, considerando las siguientes funciones recursivas sobre listas: longitud [ ] longitud (h : t ) = 0 = 1 + longitud t ( long1 ) ( long2 ) [ ] ++ l i s t a = l i s t a ( h : t ) ++ l i s t a = h : ( t ++ l i s t a ) ( concat1 ) ( concat2 ) donde: ♦ [ ] denota la lista vacía. ♦ (h:t) es una lista en la que h representa el primer elemento (head ) y t denota el resto (tail ) de la lista. ♦ ++ denota la operación de concatenación de listas. Y se desea probar la propiedad: l o n g i t u d (L ++ M) = l o n g i t u d L + l o n g i t u d M El caso base se tiene cuando L es [ ]: l o n g i t u d ( [ ] ++ M) = l o n g i t u d M longitud [ ] + longitud M = 0 + longitud M { concat1 } { long1 , a r i t m é t i c a } Ahora se debe demostrar que la propiedad se cumple cuando L es una lista no vacía. Como L es no vacía, debe ser de la forma x :xs para un elemento x y una lista xs. La hipótesis inductiva dice que la propiedad se cumple para cualquier lista M cuando L es xs: l o n g i t u d ( xs ++ M) = l o n g i t u d xs + l o n g i t u d M ( hip ) Para demostrar que la propiedad se cumple también para cualquier lista M cuando L es x :xs, se deben utilizar las definiciones de las funciones y la hipótesis inductiva: l o n g i t u d ( ( x : xs)++ M) = l o n g i t u d ( x : ( xs ++ M) ) = 1 + l o n g i t u d ( xs ++ M) = 1 + l o n g i t u d xs + l o n g i t u d M = l o n g i t u d ( x : xs ) + l o n g i t u d M { { { { concat2 } long2 } hip . } long2 } CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 9 Árboles Considerando las siguientes definiciones recursiva de árbol binario: Arbol i n t = Hoja i n t | Rama ( Arbol i n t Arbol i n t ) Y las siguientes funciones recursivas sobre esta estructura: nHojas Hoja nHojas Rama ( I D) = 1 = nHojas I + nHojas D ( hojas1 ) ( hojas2 ) nNodos Hoja nNodos Rama ( I D) = 0 = 1 + nNodos I + nNodos D ( nodos1 ) ( nodos2 ) Y se desea probar la propiedad: nHojas (A) = nNodos (A) + 1 El caso base se tiene cuando A es una Hoja: nNodos Hoja nHojas Hoja = 0 = 1 { nodos1 } { hojas1 } La hipótesis inductiva dice que la propiedad se cumple para cualquier subárbol izquierdo I y derecho D. nHojas ( I ) = nNodos ( I ) + 1 nHojas (D) = nNodos (D) + 1 ( hip1 ) ( hip2 ) Para demostrar que la propiedad se cumple también para cualquier árbol A que se compone de un subárbol izquierdo I y otro derecho D, se deben utilizar las definiciones de las funciones y la hipótesis inductiva: nHojas Rama ( I D) = nNodos Rama ( I D) + 1 nHojas I + nHojas D = nNodos ( I ) + 1 + nNodos (D) + 1 = nNodos ( I ) + nNodos (D) + 2 1 + nNodos I + nNodos D + 1 = nNodos ( I ) + nNodos (D) + 2 { { { { { hojas2 } hip1 , h i p 2 } aritmética } nodos2 } aritmética } Otra propiedad interesante es la siguiente: ♦ Si A es un árbol binario, entonces nN odos (a) ≤ 2altura(A)+1 − 1. Para probar esta propiedad, será necesario definir la función altura, que devuelve el número de nodos existentes entre la raíz y la hoja más lejana a ésta. CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 1.3 10 Programación funcional Cálculo lambda La programación funcional está inspirada en un fundamento de teoría de la computabilidad diferente a la basada en las máquinas de Turing, pero equivalente en el sentido de que puede resolver el mismo conjunto de problemas: el cálculo − λ. La sintaxis del cálculo − λ es extremadamente austera y puede expresarse en una sola línea: M ::= X| (M M ) | (λX.M ) Donde: X es un no terminal que representa una variable genérica, M es el símbolo inicial, el punto y los paréntesis son símbolos terminales. También puede darse en forma extendida: expr → | | | | constante variable expr expr (expr) λvariable.expr Las constantes son números, las variables son identificadores y operandos. El alcance de una expresión λ se extiende a la derecha hasta donde sea posible, i.e. λx.xy quiere decir (λx. (xy)) y no ((λx.x) y). Sin embargo, es común no usar la convención y utilizar los paréntesis para evitar confusiones. También es común hablar de subtérminos: (xy) es un subtérmino de (λx. (xy)), pero λx no lo es. En una expresión, cada variable puede ser libre (sin relación con una λ) o ligada (argumento de una λ). Ser libre o ligada está en función de la posición de cada variable y de su contexto (figura 1.13). Figure 1.13: Variables libres y ligadas Por ejemplo, en λx. ∗ 2 x, x es una variable ligada, puede verse como un parámetro formal de una función: ∗ 2 x es el cuerpo. El cuerpo puede ser cualquier expresión lambda válida, incluso otra función: λx.λy.∗ (+ x y) 2: “la función de x que regresa la función de y que regresa el producto de la suma de x y y con 2”. La abstracción del operador enlaza la variable sobre la que actúa, en ambos sentidos: ♦ Semánticamente: el renombramiento consistente de una variable ligada no modifica la semántica de la expresión CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 11 ♦ Sintácticamente: posibles substituciones no tienen efecto en variables ligadas. En efecto, el nombre de la variable no es importante cuando se aplica un operador λ, solo la forma en la que se utiliza. En otra palabras, dos términos se consideran equivalentes que solo difieren en el nombre de sus variables ligadas. Por ejemplo: λx.x y λy.y; o λx.λy.x y λv.λw.v. Tarea Leer: ♦ “¿Qué define un lenguaje funcional? ” (http://bit.ly/11yf3zK). • Right definition: A functional programming language is a language that emphasises programming with pure functions and immutable data. ♦ “El robot y el bebé” (http://stanford.io/1hTZNLg) de John McCarthy (http://bit.ly/XQa3HI) Y dar una pequeña opinión (no resumen) por cada lectura y por cada integrante del equipo; en el caso del cuento escrito por McCarthy, describir cómo se relaciona a la IA con otras disciplinas (computabilidad, ética, filosofía, lógica, etc.). Evaluación de expresiones lambda El cálculo lambda puro no tiene funciones predefinidas, por el momento lo usaremos de forma impura. Para evaluar (+ (∗ 5 6) (∗ 8 3)) no es posible iniciar con + porque solo opera sobre números. Antes existen dos expresiones ruducibles: (∗ 5 6) y (∗ 8 3); por tanto el proceso es el que se muestra en la figura 1.14: Figure 1.14: Evaluando una expresión Similar a derivar una sentencia a partir de una gramática. Cómputo: usando la β-reducción Para recuperar la pureza del cálculo lambda se utiliza la β-reducción (figura 1.15). Figure 1.15: β-reducción Un parámetro formal puede usarse muchas veces (figura 1.16). CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 12 Figure 1.16: Uso múltiple del parámetro Con más parámetros (figura 1.17). Figure 1.17: Múltiples parámetros Las funciones pueden ser argumentos (figura 1.18). Figure 1.18: Funciones como argumentos Hay que tener cuidado con nombres repetidos para las variables (figura 1.19). Figure 1.19: Nombres repetidos En la primer línea, la x más interna pertenece a la λ más interna, mientras la x más externa pertenece a la λ externa. Es común tratar de evitar usar el mismo nombre de variable usando la α-equivalencia (figura 1.20). CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS Figure 1.20: Uso de la α-equivalencia Cálculo lambda para programadores (http://goo.gl/pwYCyc). Cálculo lambda en el navegador (http://goo.gl/ym2dAU). 1.3.1 Dr. Racket Sitio: http://racket-lang.org/ How to Design Programs: http://bit.ly/gZOOtU Figure 1.21: Evaluación perezosa 13 CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 1.4 14 Programación lógica La programación lógica es uno de los paradigmas de programación más alejados del imperativo. Un sistema de demostración de teoremas funciona como un mecanismo computacional. La programación lógica es declarativa: un programa es una declaración de qué se desea, y no una receta de cómo obtenerlo. En la práctica, no siempre se puede alcanzar este ideal, por cuestiones de eficiencia. La fundamentación semántica de la programación lógica son los modelos de Herbrand. El lenguaje Prolog es un compromiso entre los principios de la programación lógica y la eficiencia de implementación. 1.4.1 Cláusulas definidas En principio se podría tener un lenguaje de programación lógica que abarcara todo el cálculo de predicados de primer orden. Por cuestiones de eficiencia computacional nos ocuparemos sólo de las fórmulas que se ajustan a una sintaxis particular y que se conocen como cláusulas definidas. α ← β1 , . . . , β n La fórmula α se conoce como el encabezado, mientras que β1 , . . . , βn son el cuerpo. Una fórmula puede caracer de cuerpo o de encabezado, y en este último caso se llamará meta. Un programa lógico es un conjunto de cláusulas definidas, ninguna de las cuales es una meta. Por otra parte, conviene notar que de acuerdo con nuestra notación una meta equivale a la negación del enunciado: ← β ≡ ¬β Ejemplo: 1. P12 (c, f1 (c)) ← 2. P12 (f1 (c) , f1 (c)) ← 3. P12 f1 (f1 (x)) , f12 (y, z) ← P12 (x, y) , P12 (f1 (x) , z) Interpretando la constante c como 0, las funciones f1 como sucesor y f12 como +; P12 corresponde a una relación que asocia el natural n con el n-ésimo número de Fibonacci. Para hacer más transparentes los programas lógicos, se suelen incluir símbolos que hagan obvia su interpretación. El programa anterior se puede rescribir así: 1. F ib (0, s (0)) ← 2. F ib (s (0) , s (0)) ← 3. F ib (s (s (x)) , y + z) ← F ib (x, y) , F ib (s (x) , z) CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 1.4.2 15 Resolución La resolución es una regla de inferencia que permite demostrar teoremas por medio de refutación. Supóngase que se tiene un programa lógico Γ y se quiere demostrar que una fórmula atómica α se sigue de Γ. Entonces se incluye como premisa la meta ← α (es decir, ¬α). Una demostración por resolución nos permitirá derivar fórmulas hasta que, en caso de éxito, lleguemos a una contradicción y entonces se afirmará que α se sigue de Γ. Resolución α ← β1 , . . . , βn ← α, γ1 , . . . , γk ← β1 , . . . , βn , γ1 , . . . , γk Las dos premisas son resolventes y la conclusión es la resolución. `R denota los teoremas de resolución y Res es nombre de la regla. Prog significa “cláusula del programa lógico”. Ejemplo; ♦ q ∧ r ⇒ p, q, r `R p: SWI-Prolog: http://www.swi-prolog.org Ejemplos: ♦ Gustos: % ejem . p l % c o n s u l t ( ejem ) . l e _ g u s t a ( f u l a n o , agua ) . le_gusta ( fulano , cerveza ) . le_gusta ( fulano , r e f r e s c o ) . le_gusta ( fulano , l e c h e ) . l e _ g u s t a ( zutano , c e r v e z a ) . l e _ g u s t a ( zutano , r e f r e s c o ) . l e _ g u s t a ( mengano , agua ) . l e _ g u s t a ( mengano , l e c h e ) . Consultas al intérprete: ?− ?− ?− ?− ?− le_gusta ( fulano , X) . l e _ g u s t a ( zutano , Y ) . l e _ g u s t a ( mengano , Z ) . l e _ g u s t a (X, agua ) . l e _ g u s t a (X, Y ) . ♦ Pertenencia a un rango: CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS % Y < X < Z p e r t e n e c e (X, Y, Z): − X>Y, X<Z . Consultas: ?− p e r t e n e c e ( 5 , 3 , 7 ) . ?− p e r t e n e c e ( 3 , 5 , 7 ) . N: % Sucesor suc ( 0 , 1 ) . s u c (X,Y): − X>0, Y i s X+1. % D e f i n i c i ó n de l o s n a t u r a l e s n( c ) . n ( s (X)): − n (X ) . Consultas: ?− ?− ?− ?− suc (0 ,Z ) . suc (5 ,Z ) . n( c ) . n ( s (X ) ) . ♦ Tipográficamente, la suma puede definirse como: a+0 = a a+s ( b ) = s ( a+b ) En prolog: suma ( c , Y,Y): − n (Y ) . suma ( s (X) ,Y, s (Z)): − suma (X, Y, Z ) . Consultas: ?− ?− ?− ?− suma ( s ( s ( c ) ) , s ( c ) , s ( s ( s ( c ) ) ) ) . suma ( s ( s ( c ) ) , s ( c ) , Z ) . suma ( s ( c ) ,Y, s ( s ( c ) ) ) . suma (X, s ( c ) , s ( s ( c ) ) ) . ♦ Tipográficamente, la multiplicación se define como: a·0 = 0 a · s ( b ) = ( a · b )+a En prolog: ¿? ♦ Factorial tipográfico: 16 CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 17 fact (0 , s ( 0 ) ) . f a c t ( s (X) ,Y∗ s (X)): − f a c t (X,Y ) . Consultas: ?− f a c t ( 0 ,Y ) . ?− f a c t ( s ( 0 ) ,Y ) . ?− f a c t ( s ( s ( s ( 0 ) ) ) ,Y ) . ♦ Factorial numérico: fact2 (0 ,1). f a c t 2 (X,Y): − X>0, X1 i s X−1, f a c t 2 (X1 , Y1 ) , Y i s X∗Y1 . Consultas: ?− f a c t ( 0 ,Y ) . ?− f a c t ( 6 ,X ) . ♦ Fibonacci tipográfico: f i b (0 , s ( 0 ) ) . fib ( s (0) , s (0)). f i b ( s ( s (X) ) ,Y+Z): − f i b (X,Y) , f i b ( s (X) , Z ) . Consultas: ?− f i b ( 0 ,X ) . ?− f i b ( s ( 0 ) ,X ) . ?− f i b ( s ( s ( s ( 0 ) ) ) ,X+Y ) . ♦ Fibonacci numérico: ?− ¿ ? ♦ Learn Prolog Now : http://bit.ly/hY3gM ♦ Temas de “Programación lógica e I.A.”: http://bit.ly/YGHVbT 1.5 Aplicaciones de representación y uso de conocimiento Un sistema experto es un sistema computacional que contiene el conocimiento de uno o más humanos expertos sobre un tema específico. Estos sistemas permiten guardar más conocimiento y extraerlo inteligentemente. Un sistema experto está conformado por: ♦ Base de conocimientos: contiene conocimiento modelado extraído del diálogo con un experto. CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 18 ♦ Base de hechos (memoria de trabajo): contiene los hechos que se han descubierto durante el análisis de un problema. ♦ Motor de inferencia: modela el proceso de razonamiento. ♦ Módulos de justificación: explica el razonamiento utilizado por el sistema para llegar a una determinada conclusión. ♦ Interfaz de usuario: es la interacción entre el SE y el usuario, y se realiza mediante lenguaje natural. Figure 1.22: Arquitectura básica de un sistema experto A continuación se explican con más detalle dos de los módulos más importantes en un sistema experto. 1.5.1 Base de conocimientos La base de conocimientos se expresa con reglas de lenguaje natural del tipo SI ... ENTONCES ...; por ejemplo: ♦ “SI está vivo ENTONCES es mortal” ♦ “SI edad=conocida ENTONCES año de nacimiento ← fecha actual - edad en años” ♦ “IF the identity of the germ is not known with certainty AND the germ is gram-positive AND the morphology of the organism is "rod" AND the germ is aerobic THEN there is a strong probability (0.8) that the germ is of type enterobacteriacae”(regla de Mycin) CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS 19 Esta formulación tiene la ventaja de utilizar lenguaje cotidiano (poco común en ciencia de la computación, en donde generalmente se usa algún tipo de codificación). Las reglas expresan el conocimiento que será explotado por el sistema. Existen otras maneras de formular las reglas, que no son en lenguaje común, cada regla puede adaptarse a un estilo diferente. 1.5.2 Motor de inferencia El motor de inferencia es un programa de computadora diseñado para producir un razonamiento, debe estar basado en lógica. Existe gran variedad de lógicas: proposicional, de predicados, epistémica, modal, temporal, difusa, etc. La lógica proposicional es la lógica básica de los humanos, se expresa por silogismos (http://bit.ly/IQVgA): Todos l o s hombres son m o r t a l e s Todos l o s mexicanos son hombres => Todos l o s mexicanos son m o r t a l e s Si un sistema experto utiliza esta lógica se conoce como de orden cero (zeroth-order ). Con la lógica, el sistema es capaz de generar información nueva a partir del conocimiento que contiene en la base, las reglas y los datos a procesar. El motor puede ejecutarse en dos formas: 1. En batch: en este, el sistema tiene todos los datos necesarios para procesar desde el inicio; para el usuario se comporta como un programa convencional : recibe los datos y entrega los resultados rápidamente. 2. En forma conversacional : este método se vuelve necesario cuando el desarrollaron no puede obtener toda la información al inicio; el software debe “inventar” una forma de resolver ese problema, por ejemplo, solicitar más datos al usuario, y con esto acercarse gradualmente al objetivo, tan pronto como sea posible. El resultado da la impresión de ser un diálogo con algún experto. El interés por usar lógica es que este tipo de sistemas debe ser capaz de dar una explicación clara de lo que está haciendo (el “¿Por qué?”) y que deduce (el “¿Cómo?”). Aún más, gracias a la lógica los sistemas expertos más sofisticados existentes pueden detectar contradicciones en la información que ingresa el usuario o en la base de conocimientos y explicarlas claramente. 1.5.3 Ejemplos en Prolog Prolog es un excelente lenguaje para desarrollar aplicaciones en las que se tiene conocimiento previo de algún tipo. 1.5.3.1 Árbol genealógico % Hechos hombre ( pedro ) . hombre ( paco ) . mujer ( l u z ) . mujer ( maria ) . p r o g e n i t o r ( pedro , paco ) . CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS p r o g e n i t o r ( pedro , maria ) . p r o g e n i t o r ( l u z , paco ) . p r o g e n i t o r ( l u z , maria ) . % Reglas madre (X,Y): − p r o g e n i t o r (X,Y) , mujer (X ) . padre (X,Y): − p r o g e n i t o r (X,Y) , hombre (X ) . Tarea: 1. Agregar reglas para las relaciones: ♦ Hija e hijo ♦ Abuela y abuelo ♦ Hermana y hermano ♦ Tía y tío ♦ Prima y primo 2. Añadir el conocimiento necesario para probar las relaciones anteriores. 1.5.3.2 House M.D. % Hechos t i e n e _ s i n t o m a ( manuel , f i e b r e ) . tiene_sintoma ( a l i c i a , cansancio ) . sintoma_de ( f i e b r e , g r i p e ) . sintoma_de ( t o s , g r i p e ) . sintoma_de ( c a n s a n c i o , anemia ) . elimina ( vitaminas , cansancio ) . elimina ( aspirinas , f i e b r e ) . elimina ( jarabe , tos ) . % Reglas r e c e t a r _ a (X,Y): − enfermo_de (Y,A) , a l i v i a (X,A ) . a l i v i a (X,Y): − e l i m i n a (X,A) , sintoma_de (A,Y ) . enfermo_de (X,Y): − t i e n e _ s i n t o m a (X, Z ) , sintoma_de ( Z ,Y ) . Programa: 1. Representar la red semántica de la figura: 20 CHAPTER 1. LENGUAJES DE PROGRAMACIÓN PARA RESOLVER PROBLEMAS Figure 1.23: Red semántica 2. Representar el árbol genealógico de la figura: Figure 1.24: Árbol Genealógico Para ambos: ♦ Establecer el conocimiento base ♦ Definir las reglas necesarias para determinar características recursivamente • Propiedades la red semántica • Determinar si se tuvo un ancestro de ojo azul para el árbol genealógico 21
© Copyright 2024