Download Redes Neuronales y Programación Declarativa

Document related concepts

Haskell wikipedia , lookup

Programación funcional wikipedia , lookup

Búsqueda de patrones wikipedia , lookup

Mónada (programación funcional) wikipedia , lookup

Simon Peyton Jones wikipedia , lookup

Transcript
Redes Neuronales y Programación
Declarativa
Francisco Jesús Fernández Burgos
José Manuel Cortés López
1
ÍNDICE


1. Introducción
•
•
•
1.1. Haskell y la Computación Numérica
1.2. Computación Celular
1.3. Redes Neuronales
•
1.4. Objetivos
• 1.3.1 Redes Neuronales Celulares
2. Construcción de una librería de Redes Neuronales en Haskell
•
•
•
2.1. Estructura de Datos
2.2. Análisis lineal sin Índices
2.3. Iteración y Recursión.
2
ÍNDICE

3. Definición de Capas
•
•
3.1 Definición de una Capa
3.2 Definición de múltiples Capas

4. Ventajas de Usar Haskell

5. El perceptrón Simple
•
•
•

5.1 Introducción.
5.2 Ejemplo de entrenamiento
• 5.2.1 Entrenamiento 1ªiteración
• 5.2.2 Entrenamiento 2ªiteración
5.3 Otro Ejemplo de entrenamiento sobre la máquina.
6. Estudio Comparativo: Haskell vs. Matlab
3
ÍNDICE

7. NeuroScheme
•
•
•
•
•
•
•


7.1 Introducción
7.2 Seis Razones para elegir la programación funcional
7.3 Arquitectura de la Herramienta
7.4 Interfaz con el Sistema
7.5 Interfaz Gráfica de Usuario
7.6 Implementación de RNAS
7.7 Resultados Obtenidos
8. Conclusiones
•
•
•
8.1 Conclusiones Haskell
8.2 Conclusiones NeuroScheme
8.3 Trabajo Futuro
9. Referencias
4
1. Introducción

En la actualidad, la gran mayoría del software de simulación y entrenamiento
de redes neuronales es desarrollado mediante lenguajes imperativos como
C++, Java...

Usaremos Haskell, como alternativa para la implementación de algoritmos de
simulación y entrenamiento de redes neuronales aprovechando las
potencialidades que este ofrece.

Se mostrará que es posible evitar el uso de tablas e índices para la
implementación de redes neuronales feedforward multicapa, generando un
código claro, simple y corto en comparación con los lenguajes imperativos.

Se verá como Haskell puede ser adecuado para la experimentación con
nuevos algoritmos de entrenamiento de redes neuronales gracias a su similitud
sintáctica con la matemática y las fortalezas del lenguaje.
5
1.1 Haskell y La computación
Numérica

La programación funcional ofrece la oportunidad de crear un código mucho
más comprensible y fácil de manejar gracias:
•
•
La similitud sintáctica con la matemática.
Los altos niveles de abstracción permiten crear un código más estructurado y reusable.

No se pide obtener una alta eficiencia en tiempo por parte de Haskell, pues su
misma naturaleza como lenguaje de alto nivel lo impide.

La relativa juventud de este tipo de programación ofrece un gran campo de
investigación en el cual se puede pensar en buscar nuevas alternativas:
•
•

Para la construcción de compiladores más eficientes.
Como también en la formación de alianzas con lenguajes de bajo nivel para la optimización de
algunas
operaciones
Lo importante a destacar estriba en las posibilidades que tiene Haskell para la
creación y experimentación de nuevos algoritmos en el campo de la
computación numérica.
6
1.2 Computación celular

Este nuevo paradigma computacional suministra nuevas
formas de hacer la computación más eficiente (en
términos de velocidad, coste, disipación, almacenamiento
y calidad de la solución) para tratar grandes problemas
en dominios de aplicación específicos.

La computación celular se basa en tres principios:
•
•
•
Simplicidad
Paralelismo inmenso
Localidad
7
1.3 Redes Neuronales

Una red de neuronas artificiales está caracterizada por su:
•
Arquitectura: Estructura o patrón de conexiones entre las unidades de proceso
•
Dinámica de la Computación que nos expresa el valor que toman las
unidades de proceso y que se basa en unas funciones de activación (o de
transferencia) que especifican como se transforman las señales de entrada de
la unidad de proceso en la señal de salida.
•

Algoritmo de Entrenamiento o Aprendizaje: Procedimiento para determinar
los pesos de las conexiones
Una característica muy importante de estas redes es su naturaleza
adaptativa, donde el "aprendizaje con ejemplos" sustituye a la
"programación" en la resolución de problemas.
8
1.3.1 Redes Neuronales
Celulares

Las redes neuronales celulares están constituidas
por un conjunto de unidades de proceso, llamadas
neuronas, cuyos valores posibles pueden ser discretos
o continuos y el valor que presenta cada unidad
neuronal viene dado por una función que depende de
una combinación lineal de los estados de las unidades
vecinas. (potencial sináptico)

Es por esta definición funcional que vamos a utilizar un
lenguaje funcional para describirlas.
9
1.4 Objetivos

Mostrar que es posible aprovechar la potencia expresiva de Haskell
para la definición eficiente de las redes neuronales.

Mostrar que es posible crear una aplicación, a partir de la semántica
de un lenguaje funcional, para trabajar con redes neuronales y
estudiarlas.
10
2. Construcción de una librería
de Redes Neuronales en Haskell

Para construir un algoritmo en programación funcional,
este debe ser visto como una función explicita, al cual
se le ingresa unos valores de entrada para retornar una
salida, de manera similar una red neuronal se comporta
como una función. La figura nos presenta de manera
esquemática la relación.
11
2.1 Estructura de datos


Al momento de representar una matriz haciendo uso de las programación
funcional es necesario construir una estructura de datos que haga uso de listas
en vez de tablas e índices por los siguientes motivos:
•
Las listas son la estructura lineal más importante de Haskell, además cuenta con un gran
número de funciones y operaciones para utilizarlas.
•
A parte de la poca expresividad, la representación de una matriz indexada en Haskell es
poco eficiente, ya que los valores al ser atrapados en el constructor de datos Array, son de
difícil acceso y esto tiene un costo computacional.
La siguiente figura muestra como se representa una matriz por medio de listas
de listas, donde cada una de ellas representa una fila de la matriz.
12
2.2 Álgebra Lineal sin Índices

El siguiente paso consiste en desarrollar una librería de operaciones
básicas de álgebra lineal para construir los algoritmos de entrenamiento.
Para ver el proceso de construcción observe en la siguiente figura, la
función zipmatriz se usa para definir funciones que respectivamente
sumen, resten y multipliquen elemento a elemento dos matrices, esta
abstracción hace uso del concepto de función de alto nivel.
Nota: Código de Cesar Augusto Acosta Minoli (estas funciones podrían sustituirse por funciones predefinidas en haskell)
13
2.3 Iteración y Recursión



El entrenamiento de una red neuronal es un proceso iterativo en el
cual la red actualiza sus pesos y umbrales hasta que se cumpla un
fIter se define haciendo uso de sí
criterio de
parada.
misma y hace uso de valores enteros
para
determinar
número de ciclos
de la la iteración y la
El modelo
imperativo
de el
programación
implementa
iteración
( init. yde
final).
Enylaasignaciones.
llamada
actualización
por medio
ciclos
Sin embargo la
iteración recursiva
y la actualización
de variables
se puede
init aumenta
en una unidad
y superar por medio
de declaraciones
el proceso recursivas.
termina una vez init sea igual
a final, de lo contrario sigue modificando
La figuraelmuestra
función
la cual
realiza
valor g alatravés
defIter
alguna
función
h. un proceso Iterativo
mediante la recursión.
14
2.3 Iteración y Recursión

Los elementos mencionados anteriormente son de suma importancia para
la construcción de los algoritmos de entrenamiento. A continuación se
mostrará algunos de los elementos implementados.

La implementación llevada a cabo se realizó pensando en los algoritmos
de entrenamiento de redes feedforward multicapa, la arquitectura de una
capa de este tipo de red luce como en la siguiente figura:
15
2.3 Iteración y Recursión

Por lo tanto es necesario declarar los siguientes tipos:

Los tipos Input y Target no son más que una redeclaración de una
matriz de tipo Float.
Por su parte el tipo NeuralNetwork se define como una pareja
ordenada donde el primer componente corresponde a la matriz de
pesos y la segunda componente corresponde a la matriz de
umbrales de la capa, es decir (W,b).

16
3. Definición de una capa

Para simular una capa se definió la función simlayer:

Podemos observar la gran similitud entre la descripción teórica de la red
neuronal y su implementación en haskell.
Una capa de la red multicapa que estamos desarrollando se puede
expresar como:

17
3.1.1. Función de transferencia

Un hecho interesante estriba en que la función
de activación f puede ser definida en un
modulo anterior y luego ser llamada, bien se
puede definir una arquitectura perceptrón o
una Adalaine respectivamente como :
•
simLayer p (w,b) hardlim
•
simLayer p (w,b) pureline
18
3.2 Definición de múltiples
capas

La función anterior nos permite definir una función para el
caso en el cual tenemos una arquitectura de cualquier
cantidad de capas y neuronas en cada capa.
Nota: Código de Cesar Augusto Acosta Minoli
19
4. Ventajas de usar Haskell

Un hecho interesante estriba en que la cantidad de código para la
creación de la librería es realmente pequeño en comparación con otros
lenguajes de programación. (No supera los 70K), para apreciar esto en
detalle observe el código la función trainperceptron.
Nota: Código de Cesar Augusto Acosta Minoli (Hay funciones que deberían estar mas optimizadas a la programación funcional)
20
5. El perceptrón Simple
5.1 Introducción.

Se usan en problemas de clasificación
y predicción, minimizando los errores
de clasificación incorrectos.

Arquitectura de la Red :
• N sensores de entrada : x1,x2,…,xN pertenecientes a R
• 1 unidad de proceso: y perteneciente a {0,1} ó {-1,1}

Regla de aprendizaje:
• Memorizamos p pares de patrones : {x1,z1}, {x2,z2},…,{
xp,zp}
21
5.1 El perceptrón Simple
Introducción.

Dinámica de la computación:
• y=f(u)
• θ : umbral o sesgo
•
•
•
h=
wi : pesos sinápticos asociados a xi
• función de transferencia paso → y pertenece a {0,1}
f=
• función de transferencia signo → y pertenece a {-1,1}
•
22
5.1 El perceptrón Simple
Introducción.
η(k) : tasa de aprendizaje, que optimiza la convergencia de la red.
23
5.2 Ejemplo de entrenamiento

Vamos a tener dos clases de patrones de entrada posibles
•
•
los de CLASE A
los de CLASE B

Se desea que nuestro perceptron después de un proceso de
aprendizaje pueda diferenciar patrones de ambas clases.

Patrones de entrada

•
•
clase A={ (-0.5, -0.5), (-0.5, 0.5) }
clase B={ ( 0.3, -0.5), (0.0, 1.0) }
Para ello se desea que cuando le llegue un patrón de la clase A
devuelva un 1 y cuando llegue un patrón de la clase B devuelva un 0.
•
•
Z(Clase A) = 1
Z(Clase B) = 0
24
5.2.1 Entrenamiento 1ªiteración

Inicialmente nuestra matriz de pesos sinápticos va a ser:
w1 = -0.3
w2 = 0.5
umbral = 1
• Estos valores son dados aleatoriamente, podrían haberse dado otros
cualesquiera.

1º Selecciono un patrón de entrada aleatorio, por ejemplo el patrón de
la clase B = (0.0, 1.0).

2º Calculo el potencial sináptico que le ha llegado al perceptron.
• El potencial sináptico es el resultado de sumar cada una de las entradas
del perceptron multiplicadas por el peso sináptico correspondiente para
cada entrada.
• h (x1, x2) = w1*x1 + w2*x2
• En este caso h=-0.3*0.0 + 0.5*1.0 = 0.5
25
5.2.1 Entrenamiento 1iteración

Como h es menor que el umbral (u = 1 ) entonces el perceptron
pondrá a la salida uno 0
• El perceptrón estará determinado por la función paso, es decir si
el potencial sináptico es mayor que el umbral entonces pone a la
salida un 1, de lo contrario pondrá un 0.

En este caso el PERCEPTRON HA ACERTADO (ya que le hemos
introducido un patrón de la clase B y lo ha clasificado correctamente
devolviendo un 0, luego NO SUFRE PROCESO DE
ENTRENAMIENTO.

Si se hubiese equivocado, entonces se produciría el proceso de
aprendizaje, mediante el cual se producirán cambios oportunos en los
pesos sinápticos, con objetivo de aceptar en el próximo patrón.
26
5.2.2 Entrenamiento 2iteración

Ahora cogemos otro patrón cualquiera de entrada, por ejemplo
uno de la clase A : (-0.5, 0.5)

el Potencial sináptico será:


•
h= (-0.3)*(-0.5) + 0.5 * 0.5 = 0.4
h < umbral luego entonces se devuelve un 0.
•
(Esto no coincide con la salida deseada, pues para patrones de la
clase A, es un 1.)
Puesto que se ha equivocado nuestro perceptron ha de
aprender de sus errores y modificar su matriz de pesos
sinápticos W, para asi realizar una mejor clasificación de
patrones.
27
5.2.2 Entrenamiento 2iteración


Según expusimos anteriormente:
•
•
•
•
•
•
n es el patrón de aprendizaje n=0.1
wi(1): peso sináptico de la entrada i, durante la iteración 1.
xi : valor de la entrada i
zi : Salida deseada para la entrada i
yi : Salida Obtenida para la entrada i
u(i): umbral durante la iteración i
Wi(2) = wi(1) + η [SalidaDeseada - SalidaObtenida]* xi
ósea nos quedaría que:
 w1(2) = -0.3 + η(1-0)*(-0.5) = -0.35
 w2(2) = 0.5 + η(1-0)*0.5
= 0.55
 u(2) =
1 + η(1-0)* (-1) = 0.9
28
5.2.2 Entrenamiento 2iteración

Ahora se ha producido un error en la clasificación y por lo tanto el
perceptrón aprenderá y modificara los valores de sus pesos sinápticos
y su umbral.

De forma que en sucesivas iteraciones introduciendo nuevos valores
de x1 y x2 (y -1 en el umbral) el perceptrón irá aprendiendo hasta
conseguir unos óptimos valores de w1, w2 y u , que permitan
clasificar correctamente cualquier valor introducido en la entrada.
29
5.3 Otro Ejemplo de entrenamiento
sobre la máquina.
module ParityProblem where
import Transfer
import NeuralNetwork
import Interface
p::Input
p= [[0.0,1.0,0.0,1.0,0.0,1.0,0.0,1.0],
[0.0,0.0,1.0,1.0,0.0,0.0,1.0,1.0],
[0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0]]
t:: Target
t= [[0.0,1.0,1.0,0.0,1.0,0.0,0.0,1.0]]
main = entrenarPerceptron p t (inicializar 75 [3,3,1]) 3
30
5.3 Otro Ejemplo de
entrenamiento sobre la máquina
El resultado es el siguiente:
"TrainPerceptron, Epoca 0"
"TrainPerceptron, Epoca 1"
"TrainPerceptron, Epoca 2“
Simulacion :
[0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0;
0.0 0.0 1.0 1.0 0.0 0.0 1.0 1.0]
Supervision :
[0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0;
0.0 0.0 1.0 1.0 0.0 0.0 1.0 1.0]
Desea guardar La red? y / n
31
6. Estudio comparativo: Haskell vs. Matlab

El estudio comparativo muestra que el desempeño de la
librería es bastante bueno teniendo como referente a Matlab
para los problemas propuestos.

El uso de un lenguaje funcional también muestra un código
claro, corto en comparación con los lenguajes imperativos y
sin la necesidad de usar tablas e índices.

Sin embargo se observa que requiere de mucho más tiempo
para realizar las operaciones. Lo anterior permite formular la
siguiente pregunta: ¿Cuál puede ser el papel de Haskell
frente a la computación numérica en la actualidad?
32
6. Estudio comparativo: Haskell vs. Matlab
Tiempos en Clasificación
3,5
3
Tiempo
2,5
2
Haskell
Matlab
1,5
1
0,5
0
Perceptron
Adalina
BAM
Perceptron
multicapa
BGM
Tipo de Red
Todas las pruebas se realizaron en un PC con procesador AMD-ATHLON de 850 Mhz con 256
Mb de memoria, utilizando Windows 98 como sistema operativo.
El compilador para la librería en Haskell fue GHC compiler versión 5.04.3 para Windows y los
tiempos fueron capturados mediante la función getClockTime, una función pre-definida de
Haskell.
Para la comparación se usó Matlab versión 6.1.0.450 release 12.1.
33
7. NEUROSCHEME
7.1 Introducción

Un lenguaje para el modelado de redes neuronales artificiales.

Para el desarrollo de la herramienta se ha seleccionado como
lenguaje base al dialecto Scheme.

Scheme define el ámbito de sus definiciones léxicamente, es
estructurado en bloques, soportando funciones y continuaciones como
objetos de datos que pueden ser pasados como parámetros a
funciones, retornados como el valor de una función, y permanecer
indefinidamente en memoria.
34
7.2 Seis razones para elegir la
programación funcional.
1.
La programación funcional es mucho más simple, ya que las
expresiones son construidas en forma natural inherentemente
recursiva.
2.
La programación funcional es mucho más fácil de entender, ya que
cada pieza de código ejecuta una tarea específica, facilitando el
seguimiento del código.
3.
Es posible ejecutar pruebas para validar el programa.
4.
Las variables locales se declaran directamente como parámetros de las
funciones, inicializándose siempre al ser invocada una función, y
representa más el nombre para un valor que una localización en
memoria, tal como ocurre en C o Pascal.
35
7.2 Seis razones para elegir la
programación funcional.
5.
Es posible alternar el orden de evaluación de las expresiones en los
lenguajes funcionales.
6.
Pueden construirse complejas instrucciones con retornos no locales, lo
que facilita complejas estructuras de control.
36
7.3 Arquitectura de la Herramienta

El núcleo de la herramienta es un interprete de Scheme basado en un
montículo de memoria.

La herramienta se divide en cuatro grandes partes:
•
Un módulo de interfaz con el usuario, encargado de administrar las
entradas del usuario y los mensajes del sistema, y utilitarios como la
historia de comandos.
•
Un pre-procesador de código fuente
•
Un compilador.
•
Una máquina virtual que ejecuta las instrucciones de bajo nivel
37
7.4 Interfaz con el sistema y usuarios
38
7.5 Interfaz gráfica de usuario
39
7.6 Implementación de RNAS

Se han implementado tres modelos de propagación hacia delante:
•
•
•

Feedforward.
Cascada correlación.
Cascada hacia delante.
Los algoritmos de entrenamiento implementados incluyen:
•
•
•
•
•
•
Regla delta generalizada.
Regla delta con momento.
Gradiente descendente.
Estrategias de evolución.
Temple simulado.
Así como versiones de algunas de ellas combinadas con mínimos cuadrados.
40
7.7 Resultados Obtenidos

La red neuronal se modela como un tipo de dato propio del lenguaje,
lo que implica que se encuentra codificada en lenguaje Scheme,
haciendo que la velocidad de los algoritmos de entrenamiento sea tan
alta como en las aplicaciones de usuario final disponibles hoy en día
(matlab).

La utilización de diálogos como mecanismo primario de comunicación
con el usuario de la aplicación, permite que este con unas pocas
horas de entrenamiento, y aún sin tener un conocimiento previo del
lenguaje Scheme, pueda estar entrenando modelos de redes
neuronales artificiales.
41
8. Conclusiones
En el transcurso del trabajo, podemos destacar las siguientes
conclusiones:

Se demuestra que no es necesario usar índices y tablas para el
diseño e implementación de algoritmos que simulen y entrenen redes
neuronales.

Haskell posee grandes posibilidades para el diseño y evaluación
experimental de nuevos algoritmos de forma rápida gracias al
parecido que tiene con la especificación del problema a implementar y
su similitud sintáctica con la matemática.

Los ejemplos usados para el entrenamiento mostraron que las
respuestas son de buena calidad, es confiable y se puede usar como
cualquier otro simulador de redes neuronales para resolver problemas
de tamaño moderado.
42
8.1 Conclusiones HASKELL

La implementación en Haskell permite que el código sea transparente
y ver la forma en que la librería está implementada.

Haskell permite expresar algoritmos de forma clara y simple, esto es
útil en el momento de desarrollar y de derivar versiones más eficientes
a bajo nivel.

Comparación Haskell vs. Matlab:
•
Haskell como lenguaje de programación funcional puro, aún no está
preparado para competir por la eficiencia en tiempo, su característica de
lenguaje de alto nivel le impide tal rapidez, por tal motivo:
•
Es necesario la generación de alianzas con lenguajes de bajo nivel que se
encarguen de hacer el trabajo pesado y menos significativo (es el caso del
producto de matrices.)
43
8.1 Conclusiones HASKELL
•
•
•
Como ejemplo de estas alianzas, muchos de los algoritmos de
Matlab están desarrollados en una eficiente librería de bajo nivel
diseñada para el álgebra lineal numérica (conocida como
LAPACK)
Adicionalmente Matlab hace un uso cuidadoso de C y
ensamblador en muchas de sus rutinas.
Estas alianzas han logrado optimizar significativamente las
operaciones que se pueden realizar en Matlab (véanse graficas
comparativas)
Un detalle a destacar de la librería mostrada es que no aprovecha
toda la potencia expresiva de la programación funcional, haciendo
caso omiso al sistema de clases y no aprovechando las funciones
predefinidas del lenguaje
44
8.2 Conclusiones NEUROSCHEME

El interprete implementado es altamente versátil, permitiendo construir
algoritmos para la manipulación y entrenamiento de RNAs, de tal
forma, que es posible automatizar procesos, y construir modelos
complejos de RNAs.

Los resultados obtenidos hasta ahora, permiten concluir que debe
continuarse con el desarrollo de la herramienta, e incorporarse otros
paradigmas como Redes Neurodifusas, y Sistemas Borrosos, para
habilitar la herramienta para construir sistemas híbridos para solución
de problemas.
45
8.3 Trabajo Futuro

Se hace necesario un estudio sobre las posibilidades de hacer
una alianza entre lenguajes de bajo nivel y Haskell para la
construcción de algoritmos precompilados de bajo nivel que
puedan ser usados por Haskell.

Es necesario desarrollar una interfaz visual en Haskell que
muestre de manera más amigable los resultados de
entrenamiento,(así como “plot” en Matlab) donde se puedan
apreciar graficas que muestren la evolución.

A nivel teórico es necesario observar las posibilidades que
Haskell ofrece para el diseño de nuevos algoritmos de redes
neuronales puramente funcionales, explicando la teoría de las
redes desde el lambda cálculo.
46
9. Referencias

1. Blas C. Ruiz Jiménez...[et al.], “Programación funcional
con Haskell” Málaga : Universidad, Secretariado de
Publicaciones, D.L. 1995

2. Acosta Minoli, C. A. “Artículo sobre implementación de
Redes Neuronales.”(2004) Universidad del Quindío,
Colombia.

3. Hudak et al . (2000), “A Gentle Introduction to Haskell ,
Tutorial”. http://www.haskell.org/tutorial/

4. FREEMAN, J.A. Y SKAPURA D.M,(1993) “Redes
Neuronales: Algoritmos, aplicaciones y técnicas de
programación”. Addison Wesley. 1993
47
9. Referencias

5. Martín del Brío B., A. Sanz,(2001) “Redes neuronales y
Sistemas Borrosos”. Madrid : Ra-Ma, 2ª ed.

6. HUGHES, J. (1990) “Why Functional Programming
Matters”, Institutionen för Datavetenskap, Chalmers Tekniska
Högskola, 41296 Göteborg, SWEDEN.

7. Mark P Jones, Alastair Reid, the Yale Haskell Group, and the
OGI School of Science & Engineering at OHSU, (1994-2002)
“The Hugs 98 User Manual”, web:
http://cvs.haskell.org/Hugs/pages/hugsman/index.html
48
9. Referencias

8. José R. Hilera y Victor J Martinez."REDES NEURONALES
ARTIFICIALES". Madrid : Ra-ma, [1995]

9. VELÁSQUEZ J. D., “NEUROSCHEME: UN LENGUAJE PARA EL
MODELAMIENTO DE REDES NEURONALES ARTIFICIALES”,
Grupo de Inteligencia Artificial, Facultad de Minas, Universidad
Nacional de Colombia, Junio de 2005.

10. Muñoz J., ”APUNTES DE LA ASIGNATURA MODELOS
COMPUTACIONALES”, www.lcc.uma.es/LCCTemario/Asignatura.jsp?idasignatura=22,
Escuela Superior de Ingeniería Informática, Universidad de Málaga, Abril 2006.
49