Está en la página 1de 31

6

Este captulo introduce el material base para el proyecto integrador del curso: aplicacin y adecuacin de un algoritmo para la induccin de rboles de decisin. Continuaremos nuestras prcticas de Lisp con la implementacin de un algoritmo de aprendizaje automtico bien conocido: ID3 [19, 20]. Si bien este mtodo de induccin de rboles de decisin ha sido abordado en sus cursos de Aprendizaje Automtico y Metodologas de Programacin I, haremos una revisin breve de l para entrar en materia. Este captulo ilustrar tambin algunos aspectos no funcionales de Lisp introducidos en el captulo anterior, como la lectura de lectura de archivos, el uso de libreras, la denicin de sistemas y la programacin de interfaces grcas.

ARBOLES DE DECISIN EN LISP

La Figura 6 muestra un rbol de decisin tpico. Cada nodo del rbol est conformado por un atributo y puede verse como la pregunta: Qu valor tiene este atributo en el caso que vamos a clasicar? Para este ejemplo en particular, la raz del rbol pregunta Cmo est el cielo hoy? Las ramas que salen de cada nodo coresponden a los posibles valores del atributo en cuestin. Los nodos que no tienen hijos, se conocen como hojas y representan un valor de la clase que se quiere predecir; en este caso, si juego tenis. De esta forma, un rbol de decisin representa una hiptesis sobre el atributo clase, expresada como una disyuncin de conjunciones del resto de los atributos proposicionales 1 , usados para describir nuestro problema de decisin. Cada rama del rbol representa una conjuncin de pares atributo-valor y el rbol completo es la disyuncin de esas conjunciones. Por ejemplo, la rama izquierda del rbol mostrado en la Figura 6, expresa que no se juega tenis cuando el cielo est soleado y la humedad es alta. Los rboles de decisin pueden representarse naturalmente en Lisp como una lista de listas. A continuacin denimos el rbol del ejemplo:
CL-USER> (setq arbol (cielo (soleado (humedad (normal si) (alta no))) (nublado si) (lluvia (viento (fuerte no) (debil si))))) (CIELO (SOLEADO (HUMEDAD (NORMAL SI) (ALTA NO))) (NUBLADO SI) (LLUVIA (VIENTO (FUERTE NO) (DEBIL SI))))

1 Una versin extendida para utilizar representaciones de primer orden, propuesta por Blockeel y De Raedt [1], se conoce como rbol lgico de decisin.

63

64


Cielo

Atributo Clase Valor

nublado soleado Hmedad si lluvioso Viento

alta

normal

fuerte

dbil

no

si

no

si

Una vez que hemos adoptado esta representacin, es posible denir funciones de acceso y predicados adecuados para la manipulacin del rbol. Por ejemplo:
1 2 3 4 5 6 7 8

Figura 6: Un rbol para decidir si juego tenis o no. Adaptado de Mitchell [17], p. 59.

(defun root(tree) (car tree)) (defun sub-tree(tree attribute-value) (second (assoc attribute-value (cdr tree)))) (defun leaf(tree) (atom tree))

De forma que:
CL-USER> CIELO CL-USER> (HUMEDAD CL-USER> SI CL-USER> NIL CL-USER> T (root arbol) (sub-tree arbol soleado) (NORMAL SI) (ALTA NO)) (sub-tree arbol nublado) (leaf (sub-tree arbol soleado)) (leaf (sub-tree arbol nublado))

La funcin predenida assoc busca el subrbol asociado al valor del atributo en cuestin, cuyo segundo elemento es el subrbol propiamente dicho. Consideren el siguiente ejemplo de su uso:
CL-USER> (assoc uno ((uno 1) (dos 2) (tres 3))) (UNO 1) CL-USER> (assoc dos ((uno 1) (dos 2) (tres 3))) (DOS 2)

Aunque la representacin elegida del rbol de decisin resulta natural en Lisp, diculta la su lectura por parte del usuario. Para resolver este problema, podemos escribir una funcin para desplegar el rbol de manera ms legible (Observen el uso de la macro loop):
1 2 3 4 5 6 7 8 9 10 11 12

65

(defun print-tree (tree &optional (depth 0)) (mytab depth) (format t "~A~ %" (first tree)) (loop for subtree in (cdr tree) do (mytab (+ depth 1)) (format t "- ~A" (first subtree)) (if (atom (second subtree)) (format t " -> ~A~ %" (second subtree)) (progn (terpri)(print-tree (second subtree) (+ depth 5)))))) (defun mytab (n) (loop for i from 1 to n do (format t " ")))

De forma que:
CL-USER> (print-tree arbol) CIELO - SOLEADO HUMEDAD - NORMAL -> SI - ALTA -> NO - NUBLADO -> SI - LLUVIA VIENTO - FUERTE -> NO - DEBIL -> SI NIL

Ahora bien, lo que queremos es contruir (inducir) los rboles de decisin a partir de un conjunto de ejemplos de entrenamiento. Cada ejemplo en este conjunto representa un caso cuyo valor de clase conocemos. Se busca que el rbol inducido explique los ejemplos vistos y pueda predecir casos nuevos, cuyo valor de clase desconocemos. Para el rbol mostrado en la seccin anterior los ejemplos con que fue construido se muestran en el Cuadro 1. Basados en la representacin elegida para los rboles de decisin, podramos representar los ejemplos como una lista de listas de valores para sus atributos:
CL-USER> (setq ejemplos ((SOLEADO CALOR ALTA DEBIL NO) (SOLEADO CALOR ALTA FUERTE NO) (NUBLADO CALOR ALTA DEBIL SI) (LLUVIA TEMPLADO ALTA DEBIL SI)

66


Da 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Cielo soleado soleado nublado lluvia lluvia lluvia nublado soleado soleado lluvia soleado nublado nublado lluvia

Temperatura calor calor calor templado fro fro fro templado fro templado templado templado calor templado

Humedad alta alta alta alta normal normal normal alta normal normal normal alta normal alta

Viento dbil fuerte dbil dbil dbil fuerte fuerte dbil dbil dbil fuerte fuerte dbil fuerte

Jugar-tenis? no no si si si no si no si si si si si no

Cuadro 1: Conjunto de ejemplos de entrenamiento para la clase jugar-tenis? Adaptado de Mitchell [17], p.59.
(LLUVIA FRIO NORMAL DEBIL SI) (LLUVIA FRIO NORMAL FUERTE NO) (NUBLADO FRIO NORMAL FUERTE SI) (SOLEADO TEMPLADO ALTA DEBIL NO) (SOLEADO FRIO NORMAL DEBIL SI) (LLUVIA TEMPLADO NORMAL DEBIL SI) (SOLEADO TEMPLADO NORMAL FUERTE SI) (NUBLADO TEMPLADO ALTA FUERTE SI) (NUBLADO CALOR NORMAL DEBIL SI) (LLUVIA TEMPLADO ALTA FUERTE NO)))

Pero sera ms til identicar cada ejemplo por medio de una llave y poder acceder a sus atributos por nombre, por ejemplo, preguntar por el valor del cielo en el ejemplo 7. Las siguientes funciones son la base para esta estrategia:
1 2 3 4 5

(defun put-value (attr inst val) (setf (get inst attr) val)) (defun get-value (attr inst) (get inst attr))

Su uso se ejemplica en la siguiente sesin:


CL-USER> (put-value nombre ej1 alejandro) ALEJANDRO CL-USER> (get-value nombre ej1) ALEJANDRO CL-USER> (setq *ejemplos* nil) NIL CL-USER> (push ej1 *ejemplos*) (EJ1) CL-USER> (get-value nombre (car *ejemplos*)) ALEJANDRO

Adems, como veremos ms adelante (Seccin 6.5, pgina 68), nos encontraremos con que los ejemplos de entrenamiento suelen estar almacenados en formatos que no son amigables con Lisp, de forma que un trabajo de preprocesamiento de stos ser necesario.

67

El procedimiento para clasicar un caso nuevo utilizando un rbol de decisin consiste en ltrar el ejemplo de manera ascendente, hasta encontrar una hoja que corresponde a la clase buscada. Consideren el proceso de clasicacin del siguiente caso: hCielo = soleado, T emperatura = caliente, Humedad = alta, Viento = fuertei Como el atributo Cielo, tiene el valor soleado en el ejemplo, ste es ltrado haca abajo del rbol por la rama de la izquierda. Como el atributo Humedad, tiene el valor alta, el ejemplo es ltrado nuevamente por rama de la izquierda, lo cual nos lleva a la hoja que indica la clasicacin del este nuevo caso: Jugar-tenis? = no. El Algoritmo 1 dene la funcin clasifica para rboles de decisin. Algoritmo 1 Clasica ejemplo E dado un rbol de decisin A.
1: 2: 3: 4: 5: 6: 7: 8:

function clasifica(E: ejemplo, A: rbol) Clase valor-atributo(raz(A),E); if es-hoja(raz(A)) then return Clase else clasica(E, sub-rbol(A,Clase)); end if end function

La implementacin en Lisp de este algoritmo, dada la representacin del rbol adoptada y las funciones de acceso denidas, es la siguiente:
1 2 3 4 5 6

(defun classify (instance tree) (let* ((val (get-value (root tree) instance)) (branch (sub-tree tree val))) (if (leaf branch) branch (classify instance branch))))

La mayora de los algoritmos para construir rboles de decisin son variaciones de un algoritmo inductivo bsico conocido como ID3, propuesto por Quinlan [19]; y sus versiones posteriores como C4.5 [20]. Estos algoritmos llevan a cabo una bsqueda descendente (top-down) y egosta (greedy) en el espacio de conformado por los posibles rboles de decisin.

68

ID3, denido en el Algoritmo 2, comienza por preguntarse: Qu atributo debera colocarse en la raz del rbol? Para responder esta pregunta cada atributo es evaluado usando un test estadstico, por ejemplo la ganancia de informacin, que determina que tan bien clasica el atributo en cuestin los ejemplos de entrenamiento con respecto a la clase que queremos predecir. El mejor atributo seleccionado se coloca en la raz del rbol. Para cada valor en el dominio del atributo, se crea una rama. Los ejemplos de entrenamiento se distribuyen por las ramas creadas de acuerdo al valor que tengan para el atributo de la raz. El proceso entonces se repite recursivamente para seleccionar un atributo que ser colocado en un nodo al nal de cada rama creada. Generalmente, el algoritmo se detiene si todos los ejemplos de entrenamiento comparten el mismo valor para el atributo que est siendo probado. Sin embargo, otros criterios para nalizar la bsqueda son posibles: i) Cobertura mnima, el nmero de ejemplos cubiertos por cada nodo est por abajo de cierto umbral; ii) Pruebas de signicancia estadstica, usando 2 para probar si las distribuciones de las clases en los sub-rboles diere signicativamente. Puesto que el algoritmo lleva a cabo una bsqueda egosta, slo regresa un rbol de decisin aceptable, sin reconsiderar nunca las elecciones pasadas (no hay backtracking). Por lo tanto el rbol computado puede no ser el ptimo para los ejemplos usados. Algoritmo 2 Induccin de rboles de decisin
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:

function ID3(E: ejemplos, A: atributos, C: clase) AD ; Clase clase-mayoritaria(C,E); if E = then return AD; else if misma-clase(E,C) then return AD Clase; else if A = then return AD Clase; else Mejor-Particin mejor-particin(E, A); Mejor-Atributo primero(Mejor-Particin); AD Mejor-Atributo; for all Particin resto(Mejor-Particin) do Valor-Atributo primero(Particin); Sub-E resto(Particin); agregar-rama(AD, Valor-Atributo, ID3(Sub-E, {A \ Mejor-Atributo}, C)); end for end if end function

Antes de revisar la implementacin de ste algoritmo, solucionemos la lectura de ejemplos de entrenamiento.

Normalmente, los ejemplos de entrenamiento estn almacenados en un archivo que Lisp no puede leer directamente, como una hoja de clculo, una base de datos relacional o un archivo de texto con un formato denido. La primer decisin con respecto a los ejemplos de entrenamiento, es como sern cargados en Lisp.

El formato ARFF es usado por algunas herramientas de aprendizaje automtico como Weka [24]. Esencialmente Weka usa un formato separado por comas y utiliza etiquetas para denir atributos y sus dominios (attribute, la clase (relation), comentarios ( %) y el inicio de los datos (data). El conjunto de entrenamiento sobre jugar tenis quedara representado en este formato como un archivo con extensin .arff:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

.. Formatos de los archivos

69

@RELATION jugar-tenis @ATTRIBUTE @ATTRIBUTE @ATTRIBUTE @ATTRIBUTE @ATTRIBUTE @DATA soleado, calor, alta, debil, no soleado, calor, alta, fuerte, no nublado, calor, alta, debil, si lluvia, templado, alta, debil, si lluvia, frio, normal, debil, si lluvia, frio, normal, fuerte, no nublado, frio, normal, fuerte, si soleado, templado, alta, debil, no soleado, frio, normal, debil, si lluvia, templado, normal, debil, si soleado, templado, normal, fuerte, si nublado, templado, alta, fuerte, si nublado, calor, normal, debil, si lluvia, templado, alta, fuerte, no cielo {soleado,nublado,lluvia} temperatura {calor,templado,frio} humedad {alta,normal} viento {debil,fuerte} jugar-tenis {si,no}

Tambin sera deseable que nuestro programa pudiera cargar conjuntos de entrenamiento en formato CSV (comma separate values), usado ampliamente en este contexto. En ese caso, el archivo anterior lucira como:
1 2 3 4 5 6

cielo, temperatura, humedad, viento, jugar-tenis soleado, calor, alta, debil, no soleado, calor, alta, fuerte, no nublado, calor, alta, debil, si lluvia, templado, alta, debil, si ...

.. Ambiente de aprendizaje

Primero, necesitamos declarar algunas variables globales, para identicar ejemplos, atributos y sus dominios, datos, etc. Esto congura nuestro ambiente de aprendizaje. Muchas de las funciones que deniremos necesitan tener acceso a estos valores, por ello, se denen usando defvar y la notacin usual (por convencin) de Lisp entre asteriscos:

70


1 2 3 4 5 6 7 8

;;; Global variables (defvar (defvar (defvar (defvar (defvar (defvar *examples* nil "The training set") *attributes* nil "The attributes of the problem") *data* nil "The values of the atributes of all *examples*") *domains* nil "The domain of the attributes") *target* nil "The target concept") *trace* nil "Trace the computations")

la semntica de estas variables es auto explicativa. .. Lectura de archivos

Ahora tenemos dos opciones, leer el archivo usando alguna utilera del sistema operativo en el que estamos ejecutando Lisp, por ejemplo grep en Unix; programar directamente la lectura de archivos. Primero veremos la versin programada enteramente programada en Lisp. Comencemos por una funcin que nos permita obtener una lista de cadenas de caracteres, donde cada cadena corresponde con una lnea del archivo.
1 2 3 4 5

(defun read-lines-from-file (file) (remove-if (lambda (x) (equal x "")) (with-open-file (in file) (loop for line = (read-line in nil end) until (eq line end) collect line))))

Observen el uso de loop combinado con sus formas until y collect. Algunas de las funciones que deniremos hacen uso de split-sequence que est denida en una librera no estndar de Lisp. Esto signica que ustedes tendrn que instalar la librera antes de poder compilar las deniciones listadas a continuacin. La prxima seccin aborda la instalacin y uso de libreras.
CL-USER> (read-lines-from-file "tenis.arff") ("@RELATION jugar-tenis" "@ATTRIBUTE cielo {soleado,nublado,lluvia}" "@ATTRIBUTE temperatura {calor,templado,frio}" "@ATTRIBUTE humedad {alta,normal}" "@ATTRIBUTE viento {debil,fuerte}" " @ATTRIBUTE jugar-tenis {si,no}" "soleado, calor, alta, debil, no" "soleado, calor, alta, fuerte, no" "nublado, calor, alta, debil, si" "lluvia, templado, alta, debil, si" "lluvia, frio, normal, debil, si" "lluvia, frio, normal, fuerte, no" "nublado, frio, normal, fuerte, si" "soleado, templado, alta, debil, no" "soleado , frio, normal, debil, si" "lluvia, templado, normal, debil, si" "soleado, templado, normal, fuerte, si" "nublado, templado, alta, fuerte, si" "nublado, calor, normal, debil, si" "lluvia, templado, alta, fuerte, no")

Ahora necesitamos manipular la lista de cadenas obtenida, para instanciar adecuadamente las variables de nuestro ambiente de aprendizaje. Para el caso de los archivos ARFF ests son las funciones bsicas:
1

(defun arff-get-target (lines)

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

"It extracts the value for *target* from the lines of a ARFF file" (read-from-string (cadr (split-sequence #\Space (car (remove-if-not (lambda (x) (or (string-equal "@r" (subseq x 0 2)) (string-equal "@R" (subseq x 0 2)))) lines)))))) (defun arff-get-data (lines) "It extracts the value for *data* from the lines of a ARFF file" (mapcar #(lambda(x) (mapcar #read-from-string (split-sequence #\, x))) (remove-if (lambda (x) (string-equal "@" (subseq x 0 1))) lines))) (defun arff-get-attribs-doms (lines) " It extracts the list (attibutes domains) from an ARFF file" (mapcar #(lambda(x) (list (read-from-string (car x)) (mapcar #read-from-string (split-sequence #\, (remove-if (lambda(x) (or (string-equal "{" x) (string-equal "}" x))) (cadr x)))))) (mapcar #(lambda(x) (cdr (split-sequence #\Space x))) (remove-if-not (lambda (x) (or (string-equal "@a" (subseq x 0 2)) (string-equal "@A" (subseq x 0 2)))) lines))))

71

As, podemos extraer la clase y los datos del archivo ARFF como se muestra a continuacin:
CL-ID3> (arff-get-target (read-lines-from-file "tenis.arff")) JUGAR-TENIS 11 CL-ID3> (arff-get-data (read-lines-from-file "tenis.arff")) ((SOLEADO CALOR ALTA DEBIL NO) (SOLEADO CALOR ALTA FUERTE NO) ( NUBLADO CALOR ALTA DEBIL SI) (LLUVIA TEMPLADO ALTA DEBIL SI) ( LLUVIA FRIO NORMAL DEBIL SI) (LLUVIA FRIO NORMAL FUERTE NO) ( NUBLADO FRIO NORMAL FUERTE SI) (SOLEADO TEMPLADO ALTA DEBIL NO) ( SOLEADO FRIO NORMAL DEBIL SI) (LLUVIA TEMPLADO NORMAL DEBIL SI) ( SOLEADO TEMPLADO NORMAL FUERTE SI) (NUBLADO TEMPLADO ALTA FUERTE SI) (NUBLADO CALOR NORMAL DEBIL SI) (LLUVIA TEMPLADO ALTA FUERTE NO))

72

Evidentemente, necesitamos implementar versiones de estas funciones para el caso de que el archivo de entrada est en formato CSV.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

(defun csv-get-target (lines) "It extracts the value for *target* from the lines of a CSV file" (read-from-string (car (last (split-sequence #\, (car lines)))))) (defun csv-get-data (lines) "It extracts the value for *data* from the lines of a CSV file" (mapcar #(lambda(x) (mapcar #read-from-string (split-sequence #\, x))) (cdr lines))) (defun csv-get-attribs-doms (lines) "It extracts the list (attibutes domains) from an CSV file" (labels ((csv-get-values (attribs data) (loop for a in attribs collect (remove-duplicates (mapcar #(lambda(l) (nth (position a attribs) l)) data))))) (let* ((attribs (mapcar #read-from-string (split-sequence #\, (car lines)))) (data (csv-get-data lines)) (values (csv-get-values attribs data))) (mapcar #list attribs values))))

La funcin principal para cargar un archivo e inicializar el ambiente de aprendizaje es la siguiente:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

(defun load-file (file) "It initializes the learning setting from FILE" (labels ((get-examples (data) (loop for d in data do (let ((ej (gensym "ej"))) (setf *examples* (cons ej *examples*)) (loop for attrib in *attributes* as v in d do (put-value attrib ej v)))))) (if (probe-file file) (let ((file-ext (car (last (split-sequence #\. file)))) (file-lines (read-lines-from-file file))) (reset) (cond ((equal file-ext "arff") (let ((attribs-doms (arff-get-attribs-doms file-lines))) (setf *attributes* (mapcar #car attribs-doms)) (setf *domains* (mapcar #cadr attribs-doms)) (setf *target* (arff-get-target file-lines)) (setf *data* (arff-get-data file-lines)) (get-examples *data*)

22

23 24 25 26 27 28 29 30

31

32

(format t "Training set initialized after ~s.~ %" file) )) ((equal file-ext "csv") (let ((attribs-doms (csv-get-attribs-doms file-lines))) (setf *attributes* (mapcar #car attribs-doms)) (setf *domains* (mapcar #cadr attribs-doms)) (setf *target* (csv-get-target file-lines)) (setf *data* (csv-get-data file-lines)) (get-examples *data*) (format t "Training set initialized after ~s.~ %" file) )) (t (error "Files ~s extension can not be determined." file)))) (error "File ~s does not exist.~ %" file))))

73

La funcin reset reinicia los valores de las variables globales que conguran el ambiente de aprendizaje:
1 2 3 4 5 6 7 8 9

(defun reset () (setf *data* nil *examples* nil *target* nil *attributes* nil *domains* nil *root* nil *gensym-counter* 1) (format t "The ID3 setting has been reset.~ %"))

En la distribucin del sistema todas estas deniciones se encuentran en el archivo


cl-id3-load.lisp. Con el cdigo cargado en Lisp, podemos hacer lo siguiente:
CL-ID3> (load-file "tenis.arff") The ID3 setting has been reset. Training set initialized after "tenis.arff". NIL CL-ID3> *target* JUGAR-TENIS CL-ID3> *attributes* (CIELO TEMPERATURA HUMEDAD VIENTO JUGAR-TENIS) CL-ID3> *examples* (#:|ej14| #:|ej13| #:|ej12| #:|ej11| #:|ej10| #:|ej9| #:|ej8| #:|ej7| #:|ej6| #:|ej5| #:|ej4| #:|ej3| #:|ej2| #:|ej1|)

Como pueden observar, el ambiente de aprendizaje ha sido inicializado con los datos guardados en tenis.arff. Lo mismo sucedera para tenis.csv.

Los programadores suelen quejarse de la aproximacin usada por Lisp para las libreras. De hecho, se suele asumir que no existen libreras en Lisp. Aunque esto ltimo es falso, lo cierto es que las distribuciones de Lisp no vienen acompaadas de un conjunto de libreras estandarizadas, ni existe un repositorio unicado de ellas.

74

ASDF y ASDF-install ayudan a mantener repositorios de libreras asociados a nuestra instalacin de Lisp, as como su localizacin en la web. El ndice de libreras instalables por este mtodo se encuentra en: http://www.cliki.net. Un buen tutorial est disponible en: http://common-lisp.net/project/ asdf-install/tutorial/introduction.html. Algunas distribuciones de Lisp ya incluyen ASDF, por ejemplo SBCL, Clozure y Lispworks 6.0. Si ese no es el caso, habr que instalar ASDF antes de instalar ASDF-install. Para ello hay que crear un directorio donde colocaremos el cdigo de ASDF, compilar el cdigo y cargarlo en nuestro ambiente Lisp mediante un archivo de inicializacin. En mi caso decid instalar ASDF ASDF-install en una carpeta lisp donde guardar todo sobre Lisp en mi Macbook Pro. Como Lisp utilizar Lispworks 5.1.2 Professional Edition. El procedimiento de instalacin que seguiremos es igual para cualquier sistema UNIX y similares.

.. Instalacin de ASDF En una terminal:


clea:~ aguerra$ mkdir lisp clea:~ aguerra$ cd lisp clea:lisp aguerra$ curl http://common-lisp.net/project/asdf/asdf.lisp -o asdf.lisp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 85928 100 85928 0 0 61836 0 0:00:01 0:00:01 --:--:-- 71487 clea:lisp aguerra$ ls asdf.lisp

Luego, desde una consola de Lisp, compilar el archivo adsf.lisp:


CL-USER> (change-directory "\~/lisp") #P"/Users/aguerra/lisp/" CL-USER> (compile-file "asdf.lisp") ... muchas lneas de salida T

Nuestro directorio debe incluir ahora los siguientes archivos:


clea:lisp aguerra$ ls asdf.lisp asdf.xfasl clea:lisp aguerra$

Es necesario crear una estructura de directorios donde las libreras ASDF instalables sern almacenadas permanentemente. En mi caso:
clea:lisp aguerra$ mkdir .asdf-install-dir clea:lisp aguerra$ mkdir .asdf-install-dir/site clea:lisp aguerra$ mkdir .asdf-install-dir/systems

clea:lisp aguerra$

75

Finalmente debo incluir al principio de mi archivo de inicializacin .lispworks las siguientes lneas:
1 2

#-:asdf (load "/Users/aguerra/lisp/asdf") (pushnew "/Users/aguerra/.asdf-install-dir/systems/" asdf:* central-registry* :test #equal)

esto hace que ASDF sepa donde buscar las libreras instaladas en el sistema. .. Instalacin de ASDF-install

El repositorio web de libreras ASDF instalables, es gestionado por la librera ASDF-install. Como es necesario que esta librera est instalada para bajar otras libreras, su instalacin es manual. A continuacin muestro su instalacin desde una terminal, con el directorio de trabajo en /Users/aguerra/lisp:
clea:lisp aguerra$ curl http://common-lisp.net/project/asdf-install/ asdf-install_latest.tar.gz -o asdf-install.tar.gz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 84270 100 84270 0 0 65710 0 0:00:01 0:00:01 --:--:-- 77311 clea:lisp aguerra$ gunzip asdf-install.tar.gz clea:lisp aguerra$ tar -xvf asdf-install.tar asdf-install/ asdf-install/asdf-install/ asdf-install/asdf-install/asdf-install.asd asdf-install/asdf-install/changes.text asdf-install/asdf-install/conditions.lisp ... clea:lisp aguerra$ ls -l total 1112 drwxr-xr-x 7 aguerra staff 238 Dec 26 2007 asdf-install -rw-r--r-- 1 aguerra staff 286720 Jan 12 15:13 asdf-install.tar -rw-r--r-- 1 aguerra staff 85928 Jan 12 14:39 asdf.lisp -rw-r--r-- 1 aguerra staff 194421 Jan 12 14:50 asdf.xfasl

El directorio asdf-install contiene un subdirectorio del mismo nombre, donde se ubica el archivo asdf-install.asd; eso es, la denicin del sistema en formato ASDF. Es necesario que este archivo este disponible en el registro central de ASDF, para ello creamos una liga del repositorio a este archivo:
clea:lisp aguerra$ cd clea:~ aguerra$ cd .asdf-install-dir/systems/ clea:systems aguerra$ ln -s /Users/aguerra/lisp/asdf-install/ asdf-install/asdf-install.asd . clea:systems aguerra$

76

No olviden el punto al nal de la tercera lnea. Veriquen que la herramienta tar del sistema operativo sea la versin GNU. OS X Snow Leopard instala BSD tar por defecto, por lo que es necesario cambiar eso, ya que ASDF-install depende de la salida ofrecida por la versin GNU:
clea:~ aguerra$ cd /usr/bin; sudo ln -fs gnutar tar && /usr/bin/tar --version Password: tar (GNU tar) 1.17 Copyright (C) 2007 Free Software Foundation, Inc. ... clea:usr/bin aguerra$

Finalmente agregamos una lnea al nal de nuestras modicaciones al archivo de inicializacin .lispworks, para que ASDF-install est disponible al momento de arrancar nuestro Lispworks:
1

#-:asdf-install (asdf:operate asdf:load-op :asdf-install)

.. Uso de libreras ASDF instalables

Para instalar la librera split-sequence procedemos de la siguiente manera:


CL-USER> (asdf-install:install :split-sequence) Install where? 1) System-wide install: System in /usr/local/asdf-install/site-systems/ Files in /usr/local/asdf-install/site/ 2) Personal installation: System in /Users/aguerra/.asdf-install-dir/systems/ Files in /Users/aguerra/.asdf-install-dir/site/ 0) Abort installation. --> 2 ;;; ASDF-INSTALL: Downloading 2601 bytes from http://ftp.linux.org.uk /pub/lisp/experimental/cclan/split-sequence.tar.gz to /Users/ aguerra/asdf-install-0.asdf-install-tmp ... ;;; ASDF-INSTALL: Downloading 189 bytes from http://ftp.linux.org.uk/ pub/lisp/experimental/cclan/split-sequence.tar.gz.asc to /Users/ aguerra/asdf-install-1.asdf-install-tmp ... "gpg: Firmado el Wed Jun 4 12:00:19 2003 CDT usando clave DSA ID 52 D68DF2" "[GNUPG:] SIG_ID R+qba5kYVsu0r6GQ4vjp0WCHs50 2003-06-04 1054746019" "[GNUPG:] GOODSIG 84C5E27852D68DF2 Christophe Rhodes <csr21@cam.ac.uk >" "gpg: Firma correcta de \"Christophe Rhodes <csr21@cam.ac.uk>\"" "[GNUPG:] VALIDSIG B36B91C51835DB9BFBAB735B84C5E27852D68DF2 2003-06-04 1054746019 0 3 0 17 2 00 B36B91C51835DB9BFBAB735B84C5E27852D68DF2" "[GNUPG:] TRUST_UNDEFINED"

"gpg: ATENCION: Esta clave no est certificada por una firma de confianza!" "gpg: No hay indicios de que la firma pertenezca al propietario." "Huellas dactilares de la clave primaria: B36B 91C5 1835 DB9B FBAB 735B 84C5 E278 52D6 8DF2" ;;; ASDF-INSTALL: Installing SPLIT-SEQUENCE in /Users/aguerra/. asdf-install-dir/site/, /Users/aguerra/.asdf-install-dir/systems/ "ln -s \"/Users/aguerra/.asdf-install-dir/site/split-sequence/ split-sequence.asd\" \"/Users/aguerra/.asdf-install-dir/systems/ split-sequence.asd\"" ;;; ASDF-INSTALL: Found system definition: /Users/aguerra/. asdf-install-dir/site/split-sequence/split-sequence.asd ;;; ASDF-INSTALL: Loading system ASDF-INSTALL::SPLIT-SEQUENCE via ASDF. (ASDF-INSTALL::SPLIT-SEQUENCE)

77

La librera ha sido cargada, compilada y copiada al registro central de ASDF. Si no se tiene instalado GNUPG, Lisp se quejar argumentando que no hay una rma segura que valide la operacin de instalacin. Se puede seleccionar la opcin de continuar la instalacin sin vericar la rma digital de la librera descargada. Aunque, lo mejor es instalar GNUPG. Para usar la librera instalada podemos invocarla desde la consola Lisp:
CL-USER> (asdf:operate asdf:load-op :split-sequence) #<ASDF:LOAD-OP NIL 200A68F7>

Y usarla en nuestros programas:


CL-USER> (split-sequence:split-sequence #\, "cielo,temperatura, humedad,viento") ("cielo" "temperatura" "humedad" "viento") 32

.. Quicklisp

Quicklisp es una librera pensada en hacer ms sencilla la instalacin va internet de libreras no estndar de Lisp. Al igual que que ASDF-intall, requiere que ASDF est

instalada correctamente. Pero el segundo paso es mucho ms sencillo, slo debemos: Visitar la pgina http://www.quicklisp.org/beta/ Descargar el archivo quicklisp.lisp Desde lisp, cargar el archivo anterior y evaluar la forma: (ql:add-to-init-file) Listo, Quicklisp se cargar cada vez que llamemos a Lisp! A continuacin instalaremos dos libreras que nos pueden ayudar a programar las funciones de carga de archivos: split-sequence y trivial-shell:

78


1 2 3 4 5

6 7 8 9 10 11 12

CL-USER> (ql:quickload "trivial-shell") To load "trivial-shell": Install 1 Quicklisp release: trivial-shell ; Fetching #<QL-HTTP:URL "http://beta.quicklisp.org/archive/ trivial-shell/2011-05-22/trivial-shell-20110522-http.tgz"> ; 13.61KB ================================================== 13,937 bytes in 0.00 seconds (13610.35KB/sec) ; Loading "trivial-shell" [package com.metabang.trivial-timeout]............ [package trivial-shell].. ("trivial-shell")

Y ahora podemos usar esta inferfaz con el shell del sistema operativo, para extraer las lneas del archivo ARFF mediante la utilidad de UNIX grep:
1 2 3 4 5 6 7 8 9

CL-USER> (trivial-shell:shell-command "egrep \"@ATTR\" tenis.arff") "@ATTRIBUTE cielo {soleado,nublado,lluvia} @ATTRIBUTE temperatura {calor,templado,frio} @ATTRIBUTE humedad {alta,normal} @ATTRIBUTE viento {debil,fuerte} @ATTRIBUTE jugar-tenis {si,no} " NIL 0

La librera split-sequence puede instalarse de la misma forma:


1 2 3 4 5

6 7 8 9 10 11 12 13 14 15

CL-USER> (ql:quickload "split-sequence") To load "split-sequence": Install 1 Quicklisp release: split-sequence ; Fetching #<QL-HTTP:URL "http://beta.quicklisp.org/archive/ split-sequence/2011-08-29/split-sequence-1.0.tgz"> ; 2.52KB ================================================== 2,580 bytes in 0.00 seconds (2519.53KB/sec) ; Loading "split-sequence" [package split-sequence] ("split-sequence") CL-USER> (split-sequence:split-sequence #\, "hola, que, tal") ("hola" " que" " tal") 14 CL-USER>

.. Deniendo una librera ASDF: cl-id3

En la medida que vayamos completando nuestra implementacin de ID3, el cdigo se ir haciendo ms grande. Ser conveniente separarlo en varios archivos y denir las

dependencias de compilacin entre estos usando ASDF. El siguiente listado corresponde al archivo cl-id3.asd incluido en la distribucin nal de nuestro programa.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

. : -

79

(asdf:defsystem :cl-id3 :depends-on (:split-sequence) :components ((:file "cl-id3-package") (:file "cl-id3-algorithm" :depends-on ("cl-id3-package")) (:file "cl-id3-load" :depends-on ("cl-id3-package" "cl-id3-algorithm")) (:file "cl-id3-classify" :depends-on ("cl-id3-package" "cl-id3-algorithm" "cl-id3-load")) (:file "cl-id3-cross-validation" :depends-on ("cl-id3-package" "cl-id3-algorithm" "cl-id3-load" "cl-id3-classify")) (:file "cl-id3-gui" :depends-on ("cl-id3-package" "cl-id3-algorithm" "cl-id3-load" "cl-id3-classify" "cl-id3-cross-validation"))))

Esta denicin de sistema establece que nuestra librera cl-id3 depende de la librera split-sequence, de forma que sta ltima se cargar en Lisp antes de intentar compilar y cargar nuestras fuentes. El orden en que nuestras fuentes son compiladas y cargadas se establece en los :depends-on de la denicin.

Observen que la funcin split-sequence est denida dentro del paquete del mismo nombre, de forma que si queremos llamar a est funcin desde la consola de Lisp, debemos indicar el paquete al que pertenece. Esto es necesario porque por defecto estamos ubicados en el paquete cl-user. Es conveniente denir un paquete para nuestra aplicacin de forma que las funciones relevantes no se confundan con las denidas en el paquete cl-user. A continuacin listamos el archivo cl-id3-package.lisp:
1 2 3 4 5 6 7 8 9

: -

;;; cl-id3-package ;;; The package for cl-id3 (defpackage :cl-id3 (:use :cl :capi :split-sequence) (:export :load-file :induce :print-tree :classify

80


10 11 12

:classify-new-instance :cross-validation :gui))

Esta denicin le indica a Lisp que el paquete cl-id3 hace uso de los paquetes common-lisp, capi y split-sequence, por lo que no ser necesario indicarlos al invocar sus funciones en nuestro cdigo (observen que las funciones de carga de archivos llaman a split-sequence, sin indicar a que paquete pertenece). Los smbolos denidos bajo :export son visibles desde otros paquetes. Es necesario que el resto de nuestros archivos fuente, incluyan como primer lnea lo siguiente:
1

(in-package :cl-id3)

Una vez que el sistema cl-id3 es compilado y cargado en Lisp, se puede usar como se muestra a continuacin:
CL-USER> (cl-id3:load-file "tenis.arff") The ID3 setting has been reset. Training set initialized after "tenis.arff". NIL CL-USER> (cl-id3:induce) (CIELO (SOLEADO (HUMEDAD (NORMAL SI) (ALTA NO))) (NUBLADO SI) (LLUVIA (VIENTO (FUERTE NO) (DEBIL SI))))

Volvamos a la denicin del algoritmo ID3.

La decisin central de ID3 consiste en seleccionar qu atributo colocar en cada nodo del rbol de decisin. En el algoritmo presentado, esta opcin la lleva a cabo la funcin mejor-particin, que toma como argumentos un conjunto de ejemplos de entrenamiento y un conjunto de atributos, regresando la particin inducida por el atributo, que slo, clasica mejor los ejemplos de entrenamiento. .. Particiones

Como pueden observar en la descripcin del algoritmo ID3 (Algoritmo 2, pgina 68), una operacin comn sobre el conjunto de entrenamiento es la de particin con respecto a algn atributo. La idea es tener una funcin que tome un atributo y un conjunto de ejemplos y los particione de acuerdo a los valores observados del atributo, por ejemplo:
1 2 3 4 5 6

CL-USER> (in-package :cl-id3) #<The CL-ID3 package, 148/512 internal, 7/16 external> CL-ID3> (load-file "tenis.arff") The ID3 setting has been reset. Training set initialized after "tenis.arff". NIL

7 8

CL-ID3> (get-partition temperatura *examples*) (TEMPERATURA (FRIO #:|ej5| #:|ej6| #:|ej7| #:|ej9|) (CALOR #:|ej1| #:|ej2| #:|ej3| #:|ej13|) (TEMPLADO #:|ej4| #:|ej8| #:|ej10| #:| ej11| #:|ej12| #:|ej14|))

. ?

81

Lo que signica que el atributo temperatura tiene tres valores diferentes en el conjunto de entrenamiento: fro, calor y templado. Los ejemplos 5,6,7 y 9 tienen como valor del atributo temperatura= fro, etc. La denicin de la funcin get-partition es como sigue:
1 2 3 4 5 6 7 8 9 10 11 12

(defun get-partition (attrib examples) "It gets the partition induced by ATTRIB in EXAMPLES" (let (result vlist v) (loop for e in examples do (setq v (get-value attrib e)) (if (setq vlist (assoc v result)) ;;; value v existed, the example e is added ;;; to the cdr of vlist (rplacd vlist (cons e (cdr vlist))) ;;; else a pair (v e) is added to result (setq result (cons (list v e) result)))) (cons attrib result)))

el truco est en el if de la lnea 6, que determina si el valor del atributo en el ejemplo actual es un nuevo valor o uno ya existente. Si se trata de un nuevo valor lo inserta en result como una lista (valor ejemplo). Si ya exista, rplacd se encarga de remplazar el cdr de la lista (valor ejemplo) existente, agregando el nuevo ejemplo: (valor ejemplo ejemplo-nuevo)! Necesitaremos una funcin best-partition que encuentre el atributo que mejor separa los ejemplos de entrenamiento de acuerdo a la clase buscada En qu consiste una buena medida cuantitativa de la bondad de un atributo? Para contestar a esta cuestin, deniremos una propiedad estadstica llamada ganancia de informacin. .. Entropa y ganancia de informacin

Una manera de cuanticar la bondad de un atributo en este contexto, consiste en considerar la cantidad de informacin que proveer este atributo, tal y como sto es denido en la teora de informacin de Shannon y Weaver [23]. Un bit de informacin es suciente 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 informacin, o entropa, E de la respuesta actual est dado por:
n X i= 1

E(P(vi ), . . . , P(vn )) =

P(vi ) log2 P(vi )

Consideren nuevamente el caso booleano, aplicando esta ecuacin a un volado con una moneda conable, tenemos que la probabilidad de obtener aguila o sol es de 1/2 para cada una:

82

Figura 7: Grca de la funcin entropia para clasicaciones booleanas.

1 1 1 1 1 1 E( , ) = log2 log2 =1 2 2 2 2 2 2 Ejecutar el volado nos provee 1 bit de informacin, de hecho, nos provee la clasicacin 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 informacin, menos que en el caso de la moneda justa, porque ahora tenemos ms 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 informacin, ejecutar el volado no provee informacin alguna. La grca de la funcin de entropa se muestra en la Figura 7. Consideren nuevamente los ejemplos de entrenamiento del cuadro 1 (pgina 66). De 14 ejemplos, 9 son positivos (si es un buen da para jugar tenis) y 5 son negativos. La entropia de este conjunto de entrenamiento es: 9 5 , ) = 0,940 14 14

E(

Si todos los ejemplos son positivos o negativos, por ejemplo, pertencen todos a la misma clase, la entropia ser 0. Una posible interpretacin de sto, es considerar la entropia como una medida de ruido o desorden en los ejemplos. Denimos la ganancia de informacin como la reduccin de la entropa causada por particionar un conjunto de entrenamiento S, con respecto a un atributo a: X |Sv | E(Sv ) |S| v a

Ganancia(S, a) = E(S)

Observen que el segundo trmino de Ganancia, es la entropa con respecto al atributo a. Al utilizar esta medida en ID3, sobre los ejemplos del cuadro 1, deberamos obtener algo como:
1 2 3

CL-USER> (CL-ID3> (best-partition (remove *target* *attributes*) *examples*) (CIELO (SOLEADO #:|ej1| #:|ej2| #:|ej8| #:|ej9| #:|ej11|) (NUBLADO #:|ej3| #:|ej7| #:|ej12| #:|ej13|) (LLUVIA #:|ej4| #:|ej5| #:|ej6 | #:|ej10| #:|ej14|))

Esto indica que el atributo con mayor ganancia de informacin fue cielo, de ah que esta parte del algoritmo genera la particin de los ejemplos de entrenamiento con respecto a este atributo. Si particionamos recursivamente los ejemplos que tienen el atributo cielo = soleado, obtendramos:
1 2

. ?

83

(HUMEDAD (NORMAL #:|ej9| #:|ej11|) (ALTA #:|ej1| #:|ej2| #:|ej8|))

Lo cual indica que en el nodo debajo de soleado deberamos 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, llevarn a clases terminales de nuestro problema de aprendizaje. El algoritmo terminar por construir el rbol de la gura 6. Esto nos da las pistas necesarias para programar ID3. Primero, tenemos la funcin que computa entropa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

(defun entropy (examples attrib) "It computes the entropy of EXAMPLES with respect to an ATTRIB" (let ((partition (get-partition attrib examples)) (number-of-examples (length examples))) (apply #+ (mapcar #(lambda(part) (let* ((size-part (count-if #atom (cdr part))) (proportion (if (eq size-part 0) 0 (/ size-part number-of-examples)))) (* -1.0 proportion (log proportion 2)))) (cdr partition)))))

Si queremos ahora saber la entropa del conjunto de *examples* con respecto a la clase jugar-tenis, tenemos que:
1 2

CL-USER> (entropy *examples* jugar-tenis) 0.9402859

Ahora, para computar ganancia de informacin, denimos:


1 2 3 4 5 6 7 8 9 10 11 12

(defun information-gain (examples attribute) "It computes information-gain for an ATTRIBUTE in EXAMPLES" (let ((parts (get-partition attribute examples)) (no-examples (count-if #atom examples))) (- (entropy examples *target*) (apply #+ (mapcar #(lambda(part) (let* ((size-part (count-if #atom (cdr part))) (proportion (if (eq size-part 0) 0 (/ size-part

84


13 14 15

no-examples)))) (* proportion (entropy (cdr part) *target*)))) (cdr parts))))))

de forma que la ganancia de informacin del atributo cielo, con respecto a la clase jugar-tenis, puede obtenerse de la siguiente manera:
1 2

CL-USER> (information-gain *examples* cielo jugar-tenis) 0.24674976

Ahora podemos implementar la funcin para encontrar la mejor particin con respecto a la ganancia de informacin:
1 2

3 4 5 6 7 8

9 10 11 12 13

14

15

(defun best-partition (attributes examples) "It computes one of the best partitions induced by ATTRIBUTES over EXAMPLES" (let* ((info-gains (loop for attrib in attributes collect (let ((ig (information-gain examples attrib)) (p (get-partition attrib examples))) (when *trace* (format t "Particin inducida por el atributo ~s :~ %~s~ %" attrib p) (format t "Ganancia de informacin: ~s~ %" ig)) (list ig p)))) (best (cadar (sort info-gains #(lambda(x y) (> (car x) (car y))))))) (when *trace* (format t "Best partition: ~s~ %-------------~ %" best)) best))

Si queremos encontrar la mejor particin inicial, tenemos:


1 2 3

CL-ID3> (best-partition (remove *target* *attributes*) *examples*) (CIELO (SOLEADO #:|ej1| #:|ej2| #:|ej8| #:|ej9| #:|ej11|) (NUBLADO #:|ej3| #:|ej7| #:|ej12| #:|ej13|) (LLUVIA #:|ej4| #:|ej5| #:|ej6 | #:|ej10| #:|ej14|))

Si queremos ms informacin sobre cmo se obtuvo esta particin, podemos usar la opcin de (setf *trace* t):
1 2 3 4 5 6

CL-ID3> (setf *trace* t) T CL-ID3> (best-partition (remove *target* *attributes*) *examples*) Particin inducida por el atributo CIELO: (CIELO (SOLEADO #:|ej1| #:|ej2| #:|ej8| #:|ej9| #:|ej11|) (NUBLADO #:|ej3| #:|ej7| #:|ej12| #:|ej13|) (LLUVIA #:|ej4| #:|ej5| #:|ej6 | #:|ej10| #:|ej14|)) Ganancia de informacin: 0.2467497

8 9

10 11 12

13 14 15

16 17

18 19

Particin inducida por el atributo TEMPERATURA: (TEMPERATURA (FRIO #:|ej5| #:|ej6| #:|ej7| #:|ej9|) (CALOR #:|ej1| #:|ej2| #:|ej3| #:|ej13|) (TEMPLADO #:|ej4| #:|ej8| #:|ej10| #:| ej11| #:|ej12| #:|ej14|)) Ganancia de informacin: 0.029222489 Particin inducida por el atributo HUMEDAD: (HUMEDAD (NORMAL #:|ej5| #:|ej6| #:|ej7| #:|ej9| #:|ej10| #:|ej11| #:|ej13|) (ALTA #:|ej1| #:|ej2| #:|ej3| #:|ej4| #:|ej8| #:|ej12| #:|ej14|)) Ganancia de informacin: 0.15183544 Particin inducida por el atributo VIENTO: (VIENTO (DEBIL #:|ej1| #:|ej3| #:|ej4| #:|ej5| #:|ej8| #:|ej9| #:| ej10| #:|ej13|) (FUERTE #:|ej2| #:|ej6| #:|ej7| #:|ej11| #:|ej12| #:|ej14|)) Ganancia de informacin: 0.048126936 Best partition: (CIELO (SOLEADO #:|ej1| #:|ej2| #:|ej8| #:|ej9| #:| ej11|) (NUBLADO #:|ej3| #:|ej7| #:|ej12| #:|ej13|) (LLUVIA #:|ej4 | #:|ej5| #:|ej6| #:|ej10| #:|ej14|)) ------------(CIELO (SOLEADO #:|ej1| #:|ej2| #:|ej8| #:|ej9| #:|ej11|) (NUBLADO #:|ej3| #:|ej7| #:|ej12| #:|ej13|) (LLUVIA #:|ej4| #:|ej5| #:|ej6 | #:|ej10| #:|ej14|))

. ?

85

id3 llama recursivamente a best-partition :


1 2 3 4 5 6 7 8

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

(defun id3 (examples attribs) "It induces a decision tree running id3 over EXAMPLES and ATTRIBS)" (let ((class-by-default (get-value *target* (car examples)))) (cond ;; Stop criteria ((same-class-value-p *target* class-by-default examples) class-by-default) ;; Failure ((null attribs) (target-most-common-value examples)) ;; Recursive call (t (let ((partition (best-partition attribs examples))) (cons (first partition) (loop for branch in (cdr partition) collect (list (first branch) (id3 (cdr branch) (remove (first partition) attribs)))))))))) (defun same-class-value-p (attrib value examples) "Do all EXAMPLES have the same VALUE for a given ATTRIB ?" (every #(lambda(e) (eq value (get-value attrib e))) examples)) (defun target-most-common-value (examples) "It gets the most common value for *target* in EXAMPLES"

86


30 31 32 33 34 35 36 37 38 39 40 41

(let ((domain (get-domain *target*)) (values (mapcar #(lambda(x) (get-value *target* x)) examples))) (caar (sort (loop for v in domain collect (list v (count v values))) #(lambda(x y) (>= (cadr x) (cadr y)))))))

(defun get-domain (attribute) "It gets the domain of an ATTRIBUTE" (nth (position attribute *attributes*) *domains*))

La implementacin del algoritmo termina con una pequea funcin de interfaz, para ejecutar id3 sobre el ambiente de aprendizaje por defecto. Aprovecho esta funcin para vericar si la clase est incluida en el archivo ARFF, ya que WEKA puede eliminar ese atributo del archivo . El smbolo induce es exportado por el paquete cl-id3:
1 2 3 4

(defun induce (&optional (examples *examples*)) "It induces the decision tree using learning sertting" (when (not (member *target* *attributes*)) (error "The target is defined incorrectly: Maybe Weka modified your ARFF")) (id3 examples (remove *target* *attributes*)))

De forma que para construir el rbol de decisin ejecutamos:


CL-ID3> (induce) (CIELO (SOLEADO (HUMEDAD (NORMAL SI) (ALTA NO))) (NUBLADO SI) (LLUVIA (VIENTO (FUERTE NO) (DEBIL SI))))

De forma que:
CL-ID3> (print-tree *) CIELO - SOLEADO HUMEDAD - NORMAL -> SI - ALTA -> NO - NUBLADO -> SI - LLUVIA VIENTO - FUERTE -> NO - DEBIL -> SI NIL

Aunque en realidad lo que necesitamos es una interfaz grca.

Una vez que denimos la dependencia entre los archivos de nuestro sistema va ASDF y el paquete para nuestra aplicacin va defpackage, podemos pensar en denir una

. -

87

interfaz grca para :cl-id3. Lo primero es incluir un archivo cl-id3-gui.lisp en la denicin del sistema. El paquete :cl-id3 ya hace uso de :capi, la librera para interfaces grcas de Lispworks. La interfaz nalizada se muestra en la gura 8. En esta seccin abordaremos la implementacin de la interfaz. Como es usual, la interfaz incluye una barra de mens, una ventana principal para desplegar el rbol inducido e informacin sobre el proceso de induccin; y varias ventanas auxiliares para desplegar ejemplos, atributos y dems informacin adicional. En el escritorio puede verse el icono cl-id3 (un bonsi) que permite ejecutar nuestra aplicacin. El cdigo de la interfaz se puede dividir conceptualmente en dos partes: una que incluye la denicin de los elementos grcos en ella y otra que dene el comportamiento de esos elementos en la interaccin con el usuario. .. Deniendo la interfaz

Figura 8: La interfaz grca de la aplicacin cl-id3.

Los componentes grcos de la interfaz y la manera en que estos se despliegan, se dene haciendo uso de la funcin define-interface del capi como se muestra a continuacin:
1 2 3 4 5 6 7 8

(define-interface cl-id3-gui () () (:panes (source-id-pane text-input-pane :accessor source-id-pane :text "" :enabled nil) (num-attributes-pane text-input-pane

88


9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

52 53 54 55 56 57 58 59 60 61 62

:accessor num-attributes-pane :text "" :enabled nil) (num-examples-pane text-input-pane :accessor num-examples-pane :text "" :enabled nil) (class-pane text-input-pane :accessor class-pane :text "" :enabled nil) (efficiency-pane text-input-pane :text "" :enabled nil) (k-value-pane text-input-pane :text "0") (tree-pane graph-pane :title "Decision Tree" :title-position :frame :children-function node-children :edge-pane-function #(lambda(self from to) (declare (ignore self from)) (make-instance labelled-arrow-pinboard-object :data (princ-to-string (node-from-label to)))) :visible-min-width 450 :layout-function :top-down) (state-pane title-pane :accessor state-pane :text "Welcome to CL-ID3.")) (:menus (file-menu "File" (("Open" :selection-callback gui-load-file :accelerator #\o) ("Quit" :selection-callback gui-quit :accelerator #\q))) (view-menu "View" (("Attributes" :selection-callback gui-view-attributes :accelerator #\a :enabled-function #(lambda (menu) *attributes-on *)) ("Examples" :selection-callback gui-view-examples :accelerator #\e :enabled-function #(lambda (menu) *examples-on*)))) (id3-menu "id3" (("Induce" :selection-callback gui-induce :accelerator #\i :enabled-function #(lambda (menu) *induce-on*)) ("Classify" :selection-callback gui-classify :accelerator #\k :enabled-function #(lambda (menu) *classify-on*))

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

("Cross-validation" :selection-callback gui-cross-validation :accelerator #\c :enabled-function #(lambda (menu) *cross-validation-on*)))) (help-menu "Help" (("About" :selection-callback gui-about)))) (:menu-bar file-menu view-menu id3-menu help-menu) (:layouts (main-layout column-layout (panes state-pane)) (panes row-layout (info-pane tree-pane)) (matrix-pane row-layout (confusion) :title "Confusion Matrix" :x-gap 10 :y-gap 30 :title-position :frame :visible-min-width 200) (info-pane column-layout (setting-pane id3-pane matrix-pane)) (setting-pane grid-layout ("Source File" source-id-pane "No. Attributes" num-attributes-pane "No. Examples" num-examples-pane "Class" class-pane) :y-adjust :center :title "Trainning Set" :title-position :frame :columns 2) (id3-pane grid-layout ("K value" k-value-pane "Efficiency" efficiency-pane) :y-adjust :center :title "Cross Validation" :title-position :frame :columns 2)) (:default-initargs :title "CL-ID3" :visible-min-width 840 :visible-min-height 600))

. -

89

Hay cuatro elementos a especicar en una interfaz: paneles (elementos de la interfaz que en otros lenguajes de programacin se conocen como widgets), mens, barra de mens y la composicin grca de esos elementos (layout). Tcnicamente, una interfaz es una clase con ranuras especiales para denir estos elementos y por lo tanto, la funcin define-interface es anloga a defclass. Estamos en el dominio del sistema orientado a objetos de Lip, conocido como CLOS [2, 11]. En la lnea 3, inicia la denicin de los paneles de la interfaz con la ranura :panes. Aqu se denen los elementos grcos que se utilizarn en nuestras ventanas. Por ejemplo source-id-pane (lnea 4) es un panel de entrada de texto que inicialmente despliega una cadena vaca y est deshabilitado (el usuario no puede escribir en l). El panel tree-pane (lnea 25) es un panel grco que nos permitir visualizar el rbol inducido. La funcin node-children se encargar de computar los hijos de la raz del rbol, para dibujarlos. Como deseamos que los arcos entre nodos estn etiquetados con el valor del atributo padre, redenimos el tipo de arco en la ranura :edge-pane-function (lnea 29). Como podrn deducir de la funcin annima ah denida, necesitaremos cambiar la representacin interna del rbol inducido para hacer ms sencilla su visualizacin. La lnea 37 dene un panel de tipo ttulo para implementar una barra de estado del sistema.

90

A partir de la lnea 40 denimos los mens del sistema. Todos ellos tienen shortcuts denidos en las ranuras :accelerator. La ranura :enabled-function me permite habilitar y deshabilitar los mens segn convenga, con base en las siguientes variables globales:
1 2 3 4 5

(defvar *examples-on* nil "t enables the examples menu") (defvar *attributes-on* nil "t enables the attributes menu") (defvar *induce-on* nil "t enables the induce menu") (defvar *classify-on* nil "t enables the classify menu") (defvar *cross-validation-on* nil "t enables the cross-validation menu")

Si el valor de las variables cambia a t, el men asociado se habilita. El comportamiento de los mens est denido por la funcin asociada a la ranura :selection-callback. Las funciones asociadas a esta ranura se denen ms adelante. La lnea 69 dene la barra de mens, es decir, el orden en que aparecen los mens denidos. Ahora solo nos resta denir la disposicin grca de todos estos elementos en la interfaz. Esto se especica mediante la ranura :layout a partir de la lnea 70. Usamos tres tipos de disposiciones: en columna (column-layout), en rengln (row-layout) y en rejilla (grid-layout). La columna y el rengln funcional como pilas de objetos, horizontales o verticales respectivamente. La rejilla nos permite acomodar objetos en varias columnas. El efecto es similar a denir un rengn de columnas. Finalmente la ranura default-initargs permite especicar valores iniciales para desplegar la interfaz, por ejemplo el ttulo y su tamao mnimo, tanto horizontal como vertical. .. Deniendo el comportamiento de la interfaz

En esta seccin revisaremos las funciones asociadas a las ranuras callback de los componentes de la interfaz. Estas funciones denen el comportamiento de los componentes. La siguiente funcin se hace cargo de leer archivos que denen conjuntos de entrenamiento para :cl-id3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

(defun gui-load-file (data interface) (declare (ignore data)) (let ((file (prompt-for-file nil :filter "*.arff" :filters ("WEKA files" "*.arff" "Comme Separated Values" "*.csv")))) (when file (let* ((path (princ-to-string file)) (setting (car (last (split-sequence #\/ path))))) (load-file path) (setf (text-input-pane-text (source-id-pane interface)) setting) (setf (text-input-pane-text (num-attributes-pane interface)) (princ-to-string (length *attributes*))) (setf (text-input-pane-text (num-examples-pane interface)) (princ-to-string (length *examples*)))

18 19 20 21 22 23

(setf (text-input-pane-text (class-pane interface)) (princ-to-string *target*)) (setf (title-pane-text (state-pane interface)) (format nil "The setting ~s has been loaded" path)) (setf *examples-on* t *attributes-on* t *induce-on* t)))))

. -

91

Por defecto, estas funciones reciben como argumentos data e interface cuyo contenido son los datos en el objeto de la interfaz, por ejemplo el texto capturado; y la interfaz que hizo la llamada a la funcin. La funcin predenida prompt-for-file abre un panel para seleccionar un archivo y regresa un path al archivo seleccionado. Podemos seleccionar el tipo de archivo que nos interesa entre las opciones ARFF y CSV. Posteriormente convertimos el camino al archivo en una cadena de texto con la funcin predenida princ-to-string y extraemos el nombre del archivo. La serie de setf cambia el texto asociado a los objetos en la interfaz. La ltima asignacin habilita los mens que permiten visualizar archivos y atributos, as como inducir el rbol de decisin. La siguiente funcin destruye la interfaz que la llama, esta asociada a las opciones quit de la aplicacin:
1 2 3

(defun gui-quit (data interface) (declare (ignore data)) (quit-interface interface))

El desplegado de los atributos y sus dominios se lleva a cabo ejecutando la siguiente funcin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

16 17

18 19

20 21 22 23

(defun gui-view-attributes (data interface) (declare (ignore data interface)) (let* ((max-length-attrib (apply #max (mapcar #length (mapcar #princ-to-string *attributes*)))) (pane-total-width (list character (* max-length-attrib (+ 1 (length *attributes*)))))) (define-interface gui-domains () () (:panes (attributes-pane multi-column-list-panel :columns ((:title "Attrib/Class" :adjust :left :visible-min-width (character 10) ) (:title "Attributes" :adjust :left :visible-min-width (character 20) ) (:title "Domains" :adjust :left :visible-min-width (character 20) )) :items (loop for a in *attributes* collect (list (if (eql *target* a) c a ) a (get-domain a)))

92

24 25 26 27 28 29 30 31 32

:visible-min-width pane-total-width :visible-min-height :text-height :vertical-scroll t) (button-pane push-button :text "Close" :callback gui-quit)) (:default-initargs :title "CL-ID3:attributes")) (display (make-instance gui-domains))))

Figura 9: Atributos y sus dominios desplejados en la interfaz.

En este caso usamos un multi-column-list-panel que nos permite denir columnas con encabezado. La ejecucin de esta funcin genera una ventana como la que se muestra en la gura 9. La funcin para inducir el rbol de decisin y desplegarlo grcamente es la siguiente:
1 2 3 4 5 6

(defun gui-induce (data interface) "It induces the decisicion tree and displays it in the INTERFACE" (declare (ignore data)) (setf *current-tree* (induce)) (display-tree (make-tree *current-tree*) interface))

Primero induce el rbol y despus cambia su representacin para poder dibujarlo. El cambio de representacin hace uso de nodos que incluyen la etiqueta del arco que precede a cada nodo, excepto claro la raz del rbol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

(defstruct node (inf nil) (sub-trees nil) (from-label nil)) (defun make-tree (tree-as-lst) "It makes a tree of nodes with TREE-AS-LST" (make-node :inf (root tree-as-lst) :sub-trees (make-sub-trees (children tree-as-lst)))) (defun make-sub-trees (children-lst) "It makes de subtrees list of a tree with CHILDREN" (loop for child in children-lst collect (let ((sub-tree (second child)) (label (first child))) (if (leaf-p sub-tree) (make-node :inf sub-tree

18 19 20 21

22

:sub-trees nil :from-label label) (make-node :inf (root sub-tree) :sub-trees (make-sub-trees (children sub-tree) ) :from-label label)))))

. -

93

Opcionalmente podramos cambiar nuestro algoritmo id3 para que generar el rbol con esta representacin. Las funciones para visualizar el rbol a partir de la nueva representacin son:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

(defmethod print-object ((n node) stream) (format stream "~s " (node-inf n))) (defun display-tree (root interface) "It displays the tree with ROOT in its pane in INTERFACE" (with-slots (tree-pane) interface (setf (graph-pane-roots tree-pane) (list root)) (map-pane-children tree-pane ;;; redraw panes (lambda (item) (update-pinboard-object item))))) (defun node-children (node) "It gets the children of NODE to be displayed" (let ((children (node-sub-trees node))) (when children (if (leaf-p children) (list children) children))))

La lnea 9 es necesaria para acomodar los nodos una vez que el rbol ha sido dibujado totalmente, de otra forma los desplazamientos ocurridos al dibujarlo incrementalmente, pueden desajustar su presentacin nal. La funcin node-children es la funcin asociada al panel tree-pane para computar los hijos de un nodo. La funcin print-object especica como queremos etiquetar los nodos del rbol. La siguiente funcin nos permite desplegar la interfaz grca que hemos denido, despus de limpiar la conguracin de la herramienta:
1 2 3

(defun gui () (reset) (display (make-instance cl-id3-gui)))

La llamada a esta funcin despliega la interfaz que se muestra en la gura 8.

También podría gustarte