TRABAJO DE FIN DE GRADO - Universidad de...
Transcript of TRABAJO DE FIN DE GRADO - Universidad de...
-
TRABAJO DE FIN DE GRADO
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA
UNIVERSIDAD DE SEVILLA
GRADO INGENIERÍA INFORMÁTICA – TECNOLOGÍAS INFORMÁTICAS
EVOGENET: Paralelización de la Dinámica Evolutiva de Redes Génicas
Realizado por
PEDRO ANTONIO VARO HERRERO
Dirigido por
JOSÉ LUIS GUISADO LIZAR
DEPARTAMENTO DE ARQUITECTURA Y TECNOLOGÍA DE
COMPUTADORES
ANTONIO CÓRDOBA ZURITA
DEPARTAMENTO DE FÍSICA DE LA MATERIA CONDENSADA
Sevilla, Julio de 2014
-
INDICE
1. INTRODUCCIÓN ........................................................................................................................ 3
1.1. MOTIVACIÓN .................................................................................................................... 3
1.2. OBJETIVOS ........................................................................................................................ 4
1.3. ESTRUCTURA DE LA MEMORIA ........................................................................................ 6
2. Antecedentes ........................................................................................................................... 8
2.1. BIOLOGÍA DE SISTEMAS Y REDES DE REGULACIÓN GÉNICA ............................................ 8
2.1.1. INTRODUCCIÓN A LA BIOLOGÍA DE SISTEMAS ......................................................... 8
2.1.2. REDES DE REGULACIÓN GÉNICA ............................................................................. 10
2.1.3. MODELOS MATEMÁTICOS DE REDES GÉNICAS ...................................................... 13
2.2. ARQUITECTURAS DE COMPUTADORES Y PARADIGMAS DE PROGRAMACIÓN PARALELA
16
2.2.1. ARQUITECTURAS DE COMPUTADORES .................................................................. 16
2.2.2. PARADIGMAS DE PROGRAMACIÓN PARALELA ...................................................... 20
2.3. ALGORITMO GENÉTICO .................................................................................................. 25
2.3.1. COMPONENTES DE UN ALGORITMO GENÉTICO .................................................... 26
2.3.2. PSEUDO-CÓDIGO .................................................................................................... 26
2.3.3. MODELOS PARALELOS DE ALGORITMOS GENÉTICOS ............................................ 29
2.4. PYTHON, INTRODUCCIÓN AL LENGUAJE Y FRAMEWORKS DE COMPUTACIÓN DE ALTAS
PRESTACIONES ........................................................................................................................ 33
2.4.1. PYTHON, INTRODUCCIÓN AL LENGUAJE ................................................................ 33
2.4.2. ESTUDIO SOBRE PROGRAMACIÓN PARALELA EN EL LENGUAJE PYTHON ............. 41
3. IMPLEMENTACIÓN DEL ALGORITMO GENÉTICO ................................................................... 48
3.1. PROCESO SECUENCIAL ................................................................................................... 48
3.2. PROCESO PARALELO ....................................................................................................... 56
4. ESTUDIO DE PRUEBAS Y RESULTADOS ................................................................................... 59
4.1. ESTUDIO DE ACELERACIÓN DE LA APLICACIÓN. ............................................................. 63
4.2. ESTUDIO DE ESCALABILIDAD DE LA APLICACIÓN. .......................................................... 69
4.3. ESTUDIO DE PARÁMETROS DE COMUNICACIÓN DEL ALGORITMO. .............................. 74
5. ANÁLISIS DE LA GESTIÓN Y DESARROLLO DEL TRABAJO ........................................................ 78
6. CONCLUSIONES ...................................................................................................................... 83
7. TRABAJO FUTURO .................................................................................................................. 85
8. BIBLIOGRAFÍA ......................................................................................................................... 86
Anexo A: Código fuente. ............................................................................................................. 88
-
EVOGENET: Paralelización de la Dinámica Evolutiva de Redes Génicas
2
-
1. Introducción
3
1. INTRODUCCIÓN
1.1. MOTIVACIÓN
Mi gran interés por la investigación científica en los dos últimos años de carrera ha sido
una de las principales motivaciones para la realización de este trabajo, una motivación
y afán de superación por adquirir nuevos conocimientos y poder aportar en un futuro a
la sociedad avances de la ciencia es una de mis metas.
Esta motivación me ha llevado a descubrir y estudiar cantidad de problemas
interesantes en el ámbito de la matemática, física y biología de los cuales desconocemos
la solución a infinidad de ellos y además podrían tener tantas soluciones como posibles
diferentes situaciones en la vida real. Asimismo la aplicación de la informática a estos
problemas ayuda a intentar resolver cada uno de ellos.
La biología y especialmente el estudio del comportamiento de las células, genes y ADN
es uno de esos campos científicos que más me ha llamado la atención y en el cual tengo
especial interés, además ser uno de los temas tratados en este trabajo.
Por otra parte uno de los campos de la computación en el cual tengo gran interés es en
el estudio y uso de arquitecturas alto rendimiento y técnicas de aceleración de la
ejecución, que actualmente son de gran ayuda a la hora de aplicar algoritmos y resolver
problemas complejos.
De la unión de la biología y la informática nace la bioinformática, una rama que aplica
técnicas de computación para intentar comprender los sistemas biológicos. Si por parte
de la computación le sumamos el uso de arquitecturas de alto rendimiento y por parte
de la biología ese interés por saber cómo funcionan los mecanismos básicos de los seres
crea un gran interés en este ámbito.
Por todo esto en el trabajo vamos a comenzar estudiando qué son las redes de
regulación génicas como porciones del ADN que cumplen una función determinada y
su estudio puede hacernos entender procesos celulares, la creación de nuevos genes o
la evolución de algún órgano vital. De igual forma analizaremos su modelización
matemática como redes booleanas introducidas por primera vez por Stuart Alan
Kauffman, biólogo teórico estadounidense, en 1969.
Ya que el ADN contiene tal cantidad de información en un espacio minúsculo intentar
manejar fragmentos de ADN con procesos de computación secuencial se hace una ardua
tarea, por lo que trataremos de usar arquitecturas de computación de altas prestaciones
y técnicas de aceleración para hacer más liviano todo el proceso y así poder obtener
resultados.
-
1. Introducción
4
1.2. OBJETIVOS
El objetivo de este trabajo es el desarrollo de una aplicación para determinar la dinámica
evolutiva de redes de regulación génica, mediante la implementación en paralelo de un
método basado en un algoritmo genético, introducido por D. Aguilar-Hidalgo, Mª C.
Lemos Fernández, Antonio Córdoba Zurita [1] sobre una plataforma de computación de
altas prestaciones.
La implementación se ha llevado a cabo utilizando el lenguaje Python, ya que el uso de
este lenguaje se ha elevado entre la comunidad científica, además de su potencial para
desarrollar complejas tareas en pocas líneas de código obteniendo un rendimiento más
que decente.
Para su desarrollo en paralelo se ha utilizado uno de las bibliotecas basadas en el
paradigma de programación paralela por paso por mensajes, MPI, llamada MPI4Py y
por último para el desarrollo del algoritmo genético se ha utilizado una biblioteca
especializada en computación y algoritmos evolutivos llamada DEAP.
El desarrollo de las pruebas se llevará a cabo sobre un clúster de computación de altas
prestaciones compuesto por:
Un Servidor Dell PowerEdge C6100:
3 placas base, cada una con:
2 procesadores Intel Xeon E5620 (4 cores, 2.40 GHz), DDR3-1066 MHz.
24 GB de memoria (6×4 GB RDIMM 1333 MHz).
4 discos duros de 600 GB SAS 6 Gbps 15k 3.5’’ Hot Plug.
4 discos duros de 300 GB SAS 6 Gbps 15k 3.5’’ Hot Plug.
Dos servidores Dell PowerEdge R410. Cada uno con:
2 procesadores Intel Xeon E5620 (de 4 núcleos).
24 GB de memoria (6×4 GB RDIMM 1333 MHz).
500 GB de disco duro.
Resumiendo, 10 procesadores Intel Xeon E5620 (4 núcleos, 2.40 GHz), organizados en 5
nodos con 2 procesadores en cada nodo, 8 núcleos por nodo, en conjunto hacen un total
de 40 núcleos, cada uno con una memoria cache de 12 Mb.
Además los nodos se encuentran conectados a un conmutador gigabit Ethernet con
cableado UTP RJ45.
-
1. Introducción
5
El trabajo se desarrollará cumpliendo estos objetivos:
A. Una descripción de la biología de sistemas y el ámbito de las redes de regulación
génica.
B. Un estudio de los algoritmos genéticos y sus modelos paralelos para su ejecución
en sistemas de computación de altas prestaciones.
C. Se hará un estudio del arte sobre las tecnologías de programación paralela en el
lenguaje Python, para hacernos una idea de en qué estado se encuentra.
D. La implementación del modelo de las redes de regulación génicas con un
algoritmo genético con el lenguaje de programación Python, se hará en
secuencial y paralelo, a continuación su ejecución se llevará a cabo en un sistema
de computación de altas prestaciones.
E. Por último, una vez obtenidos los resultados haremos un estudio de las mejoras
que nos ofrece la computación de altas prestaciones y la paralelización del
algoritmo.
-
1. Introducción
6
1.3. ESTRUCTURA DE LA MEMORIA
Sección 2: Antecedentes.
Donde estudiaremos algunos temas de relevancia, necesarios para el desarrollo
del trabajo.
o 2.1: Biología de Sistemas y Redes de Regulación Génica
Se describirá la rama de Biología de Sistemas, algunos conceptos
biológicos sobre genética, más en concreto las redes de regulación génica
o GRN, y el modelo matemático usado, redes booleanas. Aparte de su
interés en la biología y aplicaciones de la vida real.
o 2.2: Arquitecturas de Computadores y Paradigmas de Programación
Paralela.
Se da una visión de las arquitecturas y paradigmas de programación que
actualmente se usan para computación de altas prestaciones.
o 2.3: Algoritmos genéticos y modelos de paralelización.
Introduciremos algo de historia de estos algoritmos, explicaré los
elementos y fases de las que consta, y por último los modelos de
implementación de este algoritmo en paralelo.
o 2.4: Python, Introducción al Lenguaje y Frameworks de Computación
de Altas Prestaciones.
Se dará una visión actual del uso del lenguaje Python en la comunidad
científica e introduciremos el lenguaje de programación Python para
aquel que no lo conozca se familiarice con él. A su vez se describirá tanto
él estudio sobre programación paralela en dicho lenguaje, como el
estudio del estado del arte de frameworks de computación paralela y
computación evolutiva.
Sección 3: Implementación del Algoritmo Genético.
Donde se explicará la implementación en secuencial y en paralelo del modelo
matemático usado y el correspondiente algoritmo genético aplicado al modelo.
Sección 4: Estudio de Pruebas y Resultados.
Se hará un estudio sobre tres cuestiones fundamentales a la hora de la
paralelización como son la aceleración, su escalabilidad y algunos parámetros
propios de comunicación entre procesos.
-
1. Introducción
7
Sección 5: Análisis de la gestión y desarrollo del trabajo.
Se mostrará la división del trabajo en subtareas y su organización en el tiempo.
Sección 6: Conclusión.
Se dará una valoración final sobre el trabajo y sus posibles utilidades.
Sección 7: Trabajo Futuro.
Donde se destacarán cuestiones por hacer y posibles mejoras.
Sección 8: Bibliografía.
Se citará toda la documentación, fuentes, literatura, y web de donde se ha
recopilado toda la información.
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
8
2. ANTECEDENTES
2.1. BIOLOGÍA DE SISTEMAS Y REDES DE REGULACIÓN GÉNICA
2.1.1. INTRODUCCIÓN A LA BIOLOGÍA DE SISTEMAS
Los seres vivos tenemos un nivel de organización y complejidad de la materia
sorprendente, y creemos que toda información que describe cómo somos, crecemos y
hacemos algunas funciones vitales se encuentra codificadas en los genes.
El descubrimiento de la estructura del ADN en 1958 por Watson y Crick fue un
acontecimiento muy importante ya que a partir de ello se inició un gran interés en la
biología molecular por comprender los detalles y fundamentos del comportamiento y
evolución de los sistemas biológicos.
Desde este hecho surge un nuevo enfoque de estudio aplicado a los sistemas biológicos,
llamado Biología de Sistemas, que se centra en el estudio de las complejas interacciones
entre los componentes de los sistemas biológicos.
Para intentar comprender estas interacciones hoy en día gracias a los avances en los
laboratorios que son capaces de generar inmensas cantidades de información detallada,
tenemos a nuestro alcance grandes bancos de información acerca de los procesos
biológicos y sus interacciones, Figura 1. Además, para comprenderlos necesitamos de
principios matemáticos, físicos y químicos básicos que describan y den sentido a los
comportamientos de los sistemas biológicos.
FIGURA 1.
Conocimiento
Biología
TecnologíaComputación
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
9
Podemos diferenciar en cuatros pasos los necesarios para hacer un estudio de los
procesos que contempla la biología de sistemas
a) Tenemos que hacer un análisis de las estructuras del sistema biológico que
queremos analizar, para así definir las relaciones entre genes y las interacciones
entre proteínas.
b) Una vez obtenidas las estructuras y definidas las relaciones, estas se estudian y
obtenemos los diagramas de interacciones, tales como pueden ser redes
booleanas, obteniendo las redes de regulación genética.
c) Una vez obtenida la red de regulación génica, la estudiamos como si fuera un
grafo/red y analizamos sus propiedades topológicas.
d) Obtenemos un modelo de la red, que imita el comportamiento de ella y por el
cuál analizamos, interpretamos y predecimos resultados experimentales.
Los enlaces entre componentes moleculares de estas redes vienen dados por reacciones
químicas regidas por reglas de la química básica, al mismo tiempo estos enlaces pueden
establecer estados funcionales, es decir que realizan una función determinada y real,
definidos por procesos físicos-químicos.
Estas redes de enlaces podemos verlas como redes complejas por su alta organización,
su inmenso tamaño y su enorme cantidad de posibles enlaces y diferentes estados, que
debido a su componente biológico determinan la naturaleza de la interacción y su factor
auto-organizativo de componentes para formar de manera instantánea una red de
carácter funcional, con una función determinada.
Las características o rasgos observados en la red, llamados funciones fenotípicas, los
cuales podemos obtenerlos observando la forma en la que interactúan la multitud de
enlaces químicos proporcionan a la red una topología no lineal. Esta característica no
lineal de las redes bioquímicas hace que a medida que aumentan en tamaño, el número
de posibles estados funcionales crece más rápido que el número de componentes de la
red y además el número de características observadas en la red no guarda relación lineal
con el número de genes de la propia red.
Uno de los rasgos más importantes y esenciales de las redes biológicas y con el cual sin
ello no sería de tal interés, es su robustez, es decir, cómo el propio sistema conserva sus
propias funciones frente a diversas perturbaciones, adaptándose a cambios en el medio
o anomalías internas.
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
10
Resumiendo, la disciplina de la Biología de Sistemas estudia el comportamiento de estas
redes biológicas, así como sus enlaces, sus propiedades topológicas y su
comportamiento no lineal para intentar explicar el complejo funcionamiento que tiene
la propia naturaleza así como sus funciones fenotípicas.
2.1.2. REDES DE REGULACIÓN GÉNICA
Si examinamos la célula internamente podemos decir que está constituida por
diferentes dispositivos o membranas integrados los cuales internamente y entre ellos
tienen una inmensidad de tipos de interacciones entre proteínas, donde cada una lleva
a cabo una función muy específica con gran precisión.
FIGURA 2.
EXTRAÍDA DE: HTTP://LIFEANDEARTHSCIENCES.WIKISPACES.COM/FILE/VIEW/CELULA-EUCARIOTA.JPG
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
11
Las constante interacción con diferentes tipos de señales, como cambios de
temperatura, presión, señales moleculares de otras células, nutrientes… A las cuáles
responde mediante la generación de la proteína adecuada.
Podemos definir un gen como una región del ADN cuya secuencia codifica la información
necesaria para la formación de una proteína.
Por lo que llamamos Red de transcripción o Red de Regulación génica a la red que
determina el motivo o razón por la que se produce cada proteína. Es una red regulada
porque es el propio sistema el que decide cuándo y cómo se va a generar la proteína.
Para representar estos estados de interacción con las diferentes señales que puede
recibir una célula ya sea del exterior o del interior del organismo, las células utilizan
diferentes proteínas, llamados Factores de Transcripción.
Por consiguiente podemos decir que cuándo un gen por alguna razón, factores internos
o externos, decide transcribirse, es decir, dar respuesta a través de una proteína. Esta
transcripción se ve afectada por los factores de transcripción, los cuales pueden ser otras
proteínas anteriormente producidas por otros genes, figura 3. Se ve afectado en forma
de aceleración o disminución del ritmo de producción de la proteína, figura 3.1.
Estos Factores de Transcripción o proteínas son capaces de unirse al ADN, figura 3, para
regular el ritmo al que se produce una determinada proteína. [2] [3] [4]
FIGURA 3.
Unión de un factor de transcripción a una parte del ADN y unión con un gen
Extraida de -- http://www.biounalm.com/2012/03/la-enciclopedia-de-los-factores-de.html
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
12
FIGURA 3.1
Expresión de varios factores de transcripción con algunos genes. Extraida de [4]
A continuación vamos a describir el proceso de transcripción de un gen, es decir el
proceso mediante el cual se transporta la información necesaria de la secuencia del ADN
para generar la proteína adecuada:
De él gen que se va a transcribir, la ARN polimerasa, ARNp, extrae el código del ADN
que representa al gen. A continuación produce una molécula ARN mensajera, ARNm, a
la cual le introduce el código del gen extraído. Este ARNm es traducido/convertido en
una proteína, llamado producto genético.
Los Factores de trascripción podemos decir que son una representación interna del
medio.
Definimos el ritmo de transcripción de genes como el número de moléculas producida
de ARNm por unidad de tiempo. Este ritmo viene a estar controlado por una región
llamada cis-reguladora, la cual es una región del ADN donde se computa/decide sobre
la expresión de un gen, es decir, qué proteína es el resultado de ese gen.
Estos factores de trascripción pueden modular el ritmo de trascripción de un conjunto
de genes, es decir, modifican la probabilidad por unidad de tiempo mediante la unión
entre regiones cis-reguladoras de que los genes produzcan una cierta proteína, figura
3.1.
Como hemos dicho los factores de transcripción afectan a la tasa/ritmo por el cual la
ARNp inicia la transcripción del gen o la producción de la proteína, estos factores pueden
actuar en el ritmo de dos formas:
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
13
Activador, aumentando el ritmo de producción de proteínas
Represor, disminuyendo el ritmo de producción de proteínas o desactivándolo
sin producir alguna proteína.
Así pues podemos definir como Red de Regulación Génica como el conjunto de
interacciones entre factores transcripcionales y genes, en donde se describen todas las
interacciones entre genes en la célula. Esta red de regulación génica podemos verla de
la siguiente manera:
Los nodos son genes y los enlaces son los factores de transcripción de un gen por la
proteína producto de otro gen. Es decir, esta interacción es la unión de la proteína
producto de un gen X, con otro gen promotor Y que va a transcribir y producir otra
proteína.
Así pues podemos una como Red de Regulación Transcripcional como el conjunto de
interacciones entre Factores Transcripcionales y genes, en donde se describen todas las
interacciones de transcripciones reguladas en la célula. Esta red de regulación
transcripcional gobierna la expresión génica y podemos verla de la siguiente manera:
Los nodos son genes y los enlaces son los factores de transcripción de un gen por la
proteína producto de otro gen. Es decir, esta interacción es la unión de la proteína
producto de un gen X, con otro gen promotor Y que se va a transcribir.
2.1.3. MODELOS MATEMÁTICOS DE REDES GÉNICAS
Para la modelización de estas redes podemos encontrar tres principales modelos que se adaptan a su forma y características:
Redes Booleanas
Redes en Ecuaciones diferenciales
Método de Monte Carlo En este trabajo nos vamos a centrar en el modelo de Redes Booleanas. Las redes booleanas para tratar de modelizar las interacciones entre genes del ADN fueron introducidas por Stuart Alan Kauffman, biólogo teórico estadounidense, en 1969. En estas redes los nodos son los diferentes genes o proteína producida por los genes, los cuales pueden tener dos estados, activo o inactivo. A su vez los enlaces entre nodos de la red son los factores trancripcionales, es decir marcan el ritmo con los que el gen receptor del enlace produce dicha proteína, además como se ha mencionado anteriormente estos enlaces pueden tener carácter activador, favoreciendo al ritmo de producción de la proteína o represor disminuyendo el ritmo de producción.
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
14
En este tipo de redes los estados de los nodos vendrán dados por una expresión booleana en la que intervienen el tipo y número de enlaces. Esta red modificará su estado transcurrido el tiempo marcado como intervalos discretos, pudiendo estar la red con un estado definido en el primer intervalo y en el segundo intervalo los nodos tener un estado distinto.
FIGURA 5.
Representación de una red génica donde los nodos son los genes y los enlaces el factor de
transcripción. Los colores en los nodos representan, verde que el nodo está activado, rojo que
está desactivado. En los enlaces el color verde representa un enlace activador y el rojo un enlace
inhibidor. Extraida de [1]
-
2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica
15
FIGURA 5.1
Representa el tamaño normal de una red génica, extraída de [3]
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
16
2.2. ARQUITECTURAS DE COMPUTADORES Y PARADIGMAS DE
PROGRAMACIÓN PARALELA
Actualmente con los avances tecnológicos en laboratorios biológicos y centros de
colisiones de partículas somos capaces de generar inmensos bancos de datos con
información detallada acerca de los procesos y sistemas a los que los aplicamos.
Una vez obtenida toda esa información tenemos que operar para obtener resultados y
sacar conclusiones por lo que cada vez más, nos hace falta el uso de arquitecturas de
altas prestaciones de cómputo y el uso de técnicas de aceleración de cálculo.
Por ello las arquitecturas de los procesadores y las formas de hacer cálculos con ellos
han ido evolucionando hasta los grandes centros de datos repletos de procesadores y
gp-gpu conectadas entre ellos para poder hacer cálculos masivos en paralelo.
2.2.1. ARQUITECTURAS DE COMPUTADORES
A continuación vamos a recordar la clásica clasificación de procesadores según la
magnitud del flujo de instrucciones que pueden ejecutar y sobre cuántos flujos de datos
pueden operar a la vez, definida por Michael J. Flynn en 1972 y llamada Taxonomía de
Flynn [2] . A continuación se ilustra en la Tabla 1.
Flujos de Instrucciones
Flujos de Datos
Simple Múltiple
Simples
Single Instruction
stream Single Data
stream
(SISD)
Single Instruction stream
Multiple Data streams
(SIMD)
Múltiple
Multiple Instruction
streams Simple Data
stream
(MISD)
Multiple Instruction streams
Multiple Data streams
(MIMD)
TABLA 1. TAXONOMÍA DE FLYNN, Clasificación de computadores según Flujo de Instrucciones y Flujo
de datos capaces de ejecutar
SISD, son los procesadores capaces de operar con un único flujo de instrucciones sobre
sólo un único flujo de datos, podemos decir que son las antiguas arquitecturas
secuenciales.
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
17
MISD, este tipo de arquitectura no existe ni tiene sentido ya que aplicar varios flujos de
instrucciones a un mismo flujo de datos a la vez para conseguir un objetivo no tiene
sentido. Algunos autores consideran dentro de esta categoría a sistemas de control con
varios computadores que se aplican de forma redundante sobre los mismos datos. Por
ejemplo sistemas de aviónica.
SIMD, son las arquitecturas capaces de operar un mismo flujo de instrucciones sobre
más de un flujo de datos a la vez.
FIGURA 6. ARQUITECTURA SIMD, Representación de un flujo de instrucciones
Estas arquitecturas las podemos ver presentes en las actuales las tarjetas gráficas o
unidades de procesamiento gráfico, GP-GPU, las cuales tiene un nivel de paralelismo
muy alto, también llamado de grano fino.
Las unidades de procesamiento gráfico tienen alrededor de un número de entre 512-
2024 procesadores muy simples pero capaces de ejecutar cada uno ellos el mismo flujo
de instrucciones sobre un conjunto de flujos de datos.
MIMD, esta arquitectura está formada por varios procesadores no sincronizados
capaces de ejecutar varios flujos de instrucciones sobre múltiples flujos de datos.
Seguidamente otra clasificación que podemos hacer es según la distribución de
memoria. La cual se subdivide en dos grupos según cómo esté organizada físicamente
su memoria en el espacio, podemos llamarlos de memoria centralizada y de memoria
distribuida. Al mismo tiempo podemos hacer otra división según cómo se reparte
lógicamente el espacio de memoria, podemos llamarlos de espacio de memoria
compartida y de espacio de memoria separado. A continuación se ilustra en la tabla 2.
Disposición Lógica
Disposición Física
Espacio de memoria compartido
Espacio de memoria separado
Memoria centralizada UMA ----
Memoria Distribuida NUMA MPM
TABLA 2, Clasificación de computadores según la disposición de la memoria.
Inst.1
Data1 Data2 Data3
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
18
UMA son las siglas de “Uniform Memory Access” o Acceso Uniforme a Memoria, la
mayoría de ellos son multinúcleos, con un número de procesadores menor o igual que
32(treinta y dos), entre ellos comparten una memoria caché con una latencia uniforme,
al mismo tiempo cada núcleo tiene su propio espacio de memoria cache. El principal
cuello de botella de ellos lo tenemos en el acceso a la memoria principal. Podemos decir
que son los procesadores de uso cotidiano. A continuación se ilustra en la figura 7.
FIGURA 7, Ejemplo de arquitectura de un computador tipo UMA, extraída de [2].
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
19
NUMA son las siglas de “Non Uniform Memory Acces” o Acceso No Uniforme a
Memoria. Es una arquitectura donde hay varios procesadores conectados entre sí
mediante una red en donde cada uno de ellos comparten el mismo espacio de memoria
aún que estén en físicamente separados. Por lo que el acceso a esa memoria compartida
dependerá de en qué parte de la memoria se encuentre el dato y donde se encuentre la
memoria situada,
FIGURA 8, Ejemplo de arquitectura de un computador tipo NUMA, extraída de [2].
MPM son las siglas de “Message Passing Machine” o Maquina de Paso de Mensajes, son
sistemas en donde cada máquina tiene su sistema operativo independiente y los cuales
están conectado mediante una red de comunicación, por fibra óptica o tecnología
Ethernet. Entre ellos se comunican a través del envío de mensajes.
FIGURA 9, Ejemplo de arquitectura de un computador tipo MPM, extraída de [2] .
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
20
2.2.2. PARADIGMAS DE PROGRAMACIÓN PARALELA
Dadas las arquitecturas anteriores podemos clasificarlos en cuatro grupos según el
paradigma de programación paralela que utilicen, es decir, el tipo de tecnología
software de programación paralele que usen:
Por manejo de Hilos.
Por paso de mensajes.
Hibrida: Manejo de Hilos y paso de mensaje.
GPU-Computing
Por manejo de hilos:
Normalmente usado en arquitecturas de memoria compartida, ya que un conjunto de
hilos comparten el mismo espacio físico/lógico de memoria y su intercambio de
información, si es que lo hubiese, es directo y no impone mucha latencia en la
comunicación.
Alguna tecnología para hacer uso de este tipo de programación paralela es OpenMp [3],
la cual ofrece un completo y cómodo framework para trabajar con hilos.
FIGURA 10, Esquema de las diferentes funciones de la tecnología OpenMP, extraída de [3]
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
21
Por paso de mensajes:
Usada en arquitecturas de memoria y sistemas distribuidos para compartir la
información entre procesos, procesadores y nodos. Como su propio nombre indica se
basa en el envío de información mediante mensajes. Este paradigma ya implica un
mayor coste de tiempo a la hora de intercambiar información, ya que si aplicamos esto
a un clúster dependiendo de cómo estén conectados los nodos y la propia tecnología de
la red, el tiempo de comunicación entre nodos será mayor que si comunicamos
diferentes procesos en un mismo nodo.
Para hacer uso de este tipo de programación paralela hay estándares como MPI que nos
facilitan un framework para el paso de mensajes.
Hibrido:
Es la unión de hacer uso de computación paralela con paso de mensajes y a su vez con
control de hilos, pudiendo así ocasionar paralelismo de grano fino y grano grueso a la
vez.
Esto se puede combinar perfectamente haciendo uso de tecnologías como OpenMp con
la memoria compartida y el control de hilos y MPI para el paso de mensajes en la
compartición de información entre procesos, procesadores y en clúster.
GPU-Computing:
Es hacer uso de lo que conocemos como tarjetas gráficas y usarlas como procesadores
de propósito general para hacer cálculos con ellas. Estas nos permiten un grado de
paralelismo a nivel de datos y memoria compartida muy alto gracias a su arquitectura
diseñada con otra filosofía distinta a la de un procesador común.
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
22
FIGURA 11.
Comparación de arquitectura de CPU vs GPU, extraída de [4]
Como vemos en la figura 11 y 12, a grandes rasgos una de las principales diferencias es
que un procesador común tiene un número “pequeño” de núcleos y con funcionalidades
e instrucciones bastante complejas. Mientras que una GPU tiene un gran número de
procesadores y a su vez un inmenso número de núcleos pero con funciones e
instrucciones muy simples, tales como sumar, restar y multiplicar números.
FIGURA 12.
Comparación de arquitectura de CPU vs GPU, extraída de [4]
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
23
Existen dos tecnologías actuales para hacer uso de estas arquitecturas, OpenCL y por
parte de NVIDIA una tecnología o paradigma llamado CUDA, desde la cual vamos a ver
la arquitectura de una GPU de la siguiente forma en la figura 13.
FIGURA 13.
Arquitectura de una GP-GPU según NVIDIA CUDA, extraída de [4].
Esta define a un Grid (dispositivo gpu) como un conjunto de bloques, en donde cada uno
tiene su propia memoria compartida, sus registros e hilos. Actualmente el máximo de
hilos por bloque es de 512, lo que nos da un nivel de paralelismo muy alto. Por lo que el
número total de hilos en paralelo posibles a ejecutar vendrá determinado por el número
de bloques que tenga la arquitectura y el número de hilos que permita ejecutar cada
bloque.
Algunas consideraciones de rendimiento a la hora de utilizar este tipo de arquitectura
pueden ser [4]:
Enviar toda la información posible a la memoria de la GPU y lanzar cuantos
más hilos mejor.
Esto es para aprovechar todo el potencial de la GPU, ya que enviar
información de la memoria del sistema a la memoria de la GPU tiene
mucho coste en tiempo computacional.
-
2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela
24
Mantener el SIMD/SIMT dentro de cada bloque.
Esto significa que en todos los hilos que se lanzan se ejecute el mismo
flujo de instrucciones a la vez, ya que si un conjunto de hilos está
sumando y otro restando, estas operaciones no las va a hacer en
paralelo, primero sumará y luego restará.
Usar memoria compartida de la GPU siempre que se pueda.
Acceder "bien" a memoria global, datos contiguos.
Con esto quiero decir que se guarden datos y se accedan a ellos en
direcciones de memoria contiguas.
-
2. Antecedentes 2.3. Algoritmo Genético
25
2.3. ALGORITMO GENÉTICO
Debido a los avances en computación de altas prestaciones la gran cantidad de datos que generamos diariamente, hacen que la ejecución de algoritmos en estos sistemas para acelerar los cálculos y procesos sea actualmente un tema muy motivador, en nuestro caso vamos a centrarnos en algoritmos bio-inspirados. La biología y los procesos naturales han sido fuente de inspiración para diseñar modelos y algoritmos matemáticos para modelar, simular y resolver problemas. Entre ellos nos podemos encontrar con:
Redes Neuronales Artificiales
Algoritmos Genéticos
Algoritmos de colonias de animales
Computación con membranas, P-Sistemas.
Entre todos ellos nos vamos a centrar en los algoritmos genéticos, los cuales están basados en el proceso natural de evolución de las especies, donde los individuos se reproducen, sufre modificaciones y son sometidos a una función de evaluación. Por último, en el proceso de evolución sobreviven los que mejor se adapten al medio o mejor valor de función de evaluación tengan, favoreciendo así el elitismo de la especie. A su vez tiene un gran componente estocástico. Ya que la aplicación de estos algoritmos suelen ser a problemas de optimización tiene sentido favorecer el elitismo, ya que queremos encontrar la mejor solución a nuestro problema.
-
2. Antecedentes 2.3. Algoritmo Genético
26
2.3.1. COMPONENTES DE UN ALGORITMO GENÉTICO
Los algoritmos genéticos están compuestos de varios componentes que hacen que tengan una terminología especial a la hora de hablar de ellos, a continuación pasamos a describir esta terminología.
Genes, material genético básico o forma de representar nuestro problema a
resolver.
Individuos, secuencia de genes que codifica un estado/solución al problema.
Población, conjunto de individuos o diferentes soluciones, esta va ir
evolucionando según generaciones.
Función de evaluación, o función objetico nos evalúa los individuos de cada
población, obteniendo un valor donde distinguimos si la solución es lo bastante
buena para nuestro problema. Este va a ser nuestro valor a optimizar.
2.3.2. PSEUDO-CÓDIGO
1 Crear Población inicial.
2 Evaluar población inicial.
3 Repetir hasta condición de parada
3.1 Seleccionamos padres.
3.2 Aplicamos operador Entrecruzamiento sobre la pareja de padres elegida.
3.3 Aplicamos operador Mutación sobre toda la nueva población generada.
3.4 Evaluamos los nuevos individuos.
3.5 Seleccionamos individuos para la siguiente generación.
A continuación vamos a describir cada paso del Pseudo-código y cómo podemos desarrollarlo, ya que dependiendo de nuestro problema a resolver y la forma de representarlo, existen diferentes métodos que se adaptarán mejor o peor a nuestro caso:
Crear Población inicial:
Inicializamos la población aleatoriamente o con algún criterio definido. Debemos generar individuos que sean soluciones válidas a nuestro problema, ya sean buenas o malas.
-
2. Antecedentes 2.3. Algoritmo Genético
27
Evaluamos la población inicial: Con la función de evaluación o función objetivo definida evaluamos la población inicial creada, que dependerá de cada problema. Por ejemplo si nuestro objetivo es encontrar el mínimo de una función no derivable o muy difícil de derivar, nuestra función objetivo será la propia función, pero si estamos resolviendo un problema como el del Viajante de comercio (TSP), nuestra función objetivo deberá ser la distancia recorrida en la solución actual.
Iniciamos un bucle con un criterio de parada:
Iniciamos el bucle principal donde el criterio de parada puede ser desde un número de generaciones hasta conseguir llegar a un valor de la función de evaluación deseado. En este tipo de algoritmos si nuestra función objetivo tiene muchos mínimos locales la única forma de llegar a conseguir un mejor valor de evaluación es optando por un número de generaciones más grande.
Seleccionamos padres:
Hacemos una selección de individuos padres para comenzar el proceso de generación de nuevos individuos hijo y modificación de genes. Esta selección la podemos hacer desde aleatoria hasta con diferentes criterios de selección. Por selección de torneo, seleccionar los mejores a pares o por selección de ruleta, la selección del individuo será directamente proporcional a su valor de la función de evaluación.
Operador Entrecruzamiento:
Con la anterior selección de padres, mediante un proceso definido y una componente estocástica-, generamos nuevos individuos, que serán los hijos de estos. Este proceso lo haremos según nos convenga y según la representación de nuestros genes e individuos, por ejemplo marcar uno o varios pivotes aleatorios e intercambiar las partes.
Operador Mutación:
Los individuos o hijos anteriormente creados, le aplicamos una modificación en los genes, es decir alteramos el contenido de la solución mediante un proceso definido y una componente estocástica cómo una probabilidad de mutación.
Evaluamos los nuevos individuos:
A los nuevos individuos o, hijos creados, les aplicamos la función de evaluación para saber cuál es su valor o como de buenos son.
-
2. Antecedentes 2.3. Algoritmo Genético
28
Seleccionamos individuos para la siguiente generación:
Entre los padres e hijos creados, elegimos mediante un proceso definido un número de ellos para que sean los individuos padres de la siguiente generación, normalmente se pasan a cada generación los mejores o los que mejor valor de la función objetivo tenga favoreciendo así el elitismo. Finalmente cuando tengamos definida la condición de parada nos quedaremos con la última población de individuos y entre ellos con los que mejor valor de la función de evaluación tengan.
A continuación en la figura 14 se muestra un diagrama de flujo del algoritmo genético.
FIGURA 14.
Esquema general de ejecución de un algoritmo Genético.
Condición de parada
Selección de padres
Aplicamos operador crossover
Aplicamos operador mutación
Evaluamos la nueva población
Seleccionamos individuos para
siguiente generación
Iniciamos población inicial
Evaluamos población inicial
-
2. Antecedentes 2.3. Algoritmo Genético
29
2.3.3. MODELOS PARALELOS DE ALGORITMOS GENÉTICOS
Una vez hemos descrito cada una de las partes y terminología de un algoritmo genético, vamos a pasar a describir los actuales modelos paralelos para la implementación en sistemas de computación de altas prestaciones. Actualmente existen principalmente dos modelos usados, el modelo en Islas y el modelo celular. El modelo en Islas está diseñado para una mejor ejecución en sistemas de memoria distribuida, mientras que el modelo celular está pensado para sistemas de memoria compartida [5] [6]. A continuación vamos a describir cada uno.
Modelo Celular A este modelo se le da este nombre por su semejanza con los autómatas celulares. Si consideramos nuestra población como una matriz, en donde cada posición es un individuo de la población, decimos que nuestros individuos tan sólo se pueden comunicar con sus individuos vecinos, Figura 15. Esto implica que el algoritmo al evolucionar genere zonas de evolución aisladas.
FIGURA 15. Población celular, ejemplo de vecindad en el modelo celular de un algoritmo genético
Este modelo se da más para una arquitectura de memoria compartida y en donde se puedan ejecutar varios hilos a la vez, ya que podríamos hacer las operaciones de entrecruzamiento, mutación y evaluación de los individuos en paralelo, pudiendo asignar a cada hilo un individuo de la población.
-
2. Antecedentes 2.3. Algoritmo Genético
30
Modelo en Islas
También llamado modelo distribuido o multi-población, tiene presente la idea de tener varias subpoblaciones que llevan a cabo un intercambio de individuos entre ellas. Cada población corresponde a una ejecución del algoritmo, cada una de ellas en una isla distinta como podemos observar en la Figura 16.
FIGURA 16.
Modelos en islas, ilustración del modelo en islas de un algoritmo genético con cinco islas
En un clúster de N núcleos conectados entre sí podríamos ejecutar N instancias del algoritmo genético y compartir individuos entre ellas.
Isla 1
Isla 2
Isla 3Isla 4
Isla 5
-
2. Antecedentes 2.3. Algoritmo Genético
31
Lo que nos lleva a determinar una serie de parámetros de los que dependerá nuestra ejecución.
Número de generaciones para intercambiar información.
Ya que este modelo contempla la idea del intercambio de individuos entre islas tenemos que definir y estudiar cada cuántas generaciones hacemos el intercambio de individuos entre islas.
Número de individuos que intercambiamos entre islas.
Otro parámetro a definir y estudiar sería cuantos individuos intercambiamos entre islas.
Como elegimos a los individuos que enviamos.
A su vez debemos definir cómo vamos a elegir a los individuos que enviamos proponiendo algunos criterios.
Eligiendo a los mejores individuos con respecto a su función de evaluación.
Eligiendo individuos aleatoriamente entre la población.
Eligiendo por el método de la ruleta, donde la probabilidad de elegir a un
individuo es directamente proporcional al valor de su función de evaluación.
Que individuos reemplazamos por los que nos llegan.
Al igual que anteriormente hemos definido que individuos vamos a enviar, tenemos que definir qué individuos vamos a reemplazar por los que nos llegan proponiendo algunos criterios.
Reemplazar a los peores individuos con respecto a su función de evaluación.
Reemplazar individuos aleatoriamente.
A qué isla le enviamos nuestro individuo.
También podemos pensar a qué isla enviar la información. Normalmente la islas se nombran por el número del proceso y su intercambio se hace con el proceso siguiente, enviando el último proceso al primero.
-
2. Antecedentes 2.3. Algoritmo Genético
32
Cuántas islas ejecutamos
Por último un parámetro a estudiar es el número de algoritmos genéticos, islas o procesos a ejecutar ya que de ello depende el nivel de paralelización y uso de la arquitectura distribuida.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
33
2.4. PYTHON, INTRODUCCIÓN AL LENGUAJE Y FRAMEWORKS DE
COMPUTACIÓN DE ALTAS PRESTACIONES
En esta sección vamos a dar una visión global acerca del aumento del uso de este
lenguaje en la comunidad científica, además haremos una pequeña introducción sobre
los elementos básicos del lenguaje de programación y por último se hará un estudio del
estado del arte de librerías de computación de altas prestaciones que utilicen Python.
2.4.1. PYTHON, INTRODUCCIÓN AL LENGUAJE
Python en es un lenguaje que fue creado a finales de los años ochenta por el científico
de la computación holandés Guido van Rossum [7] donde su filosofía es hacer que el
código sea fácilmente legible, por lo que es tabulado.
Python agrupa tres paradigmas de programación:
Programación orientada a objetos, en Python todo es un objeto.
Programación declarativa o imperativa
Programación funcional
Además es un lenguaje interpretado, por lo que no hace falta compilarlo, tan sólo un
intérprete de código, el intérprete de Python. Como colector de basura/memoria tiene
conteo de referencias. Los datos son débilmente tipados y dinámicos, esto quiere decir
que las variables pueden tomar distintos tipos de datos a lo largo de la ejecución. A su
vez no tenemos que definir el tipo de datos de las variables, es responsabilidad del
programador el tratar las variables correctamente según su uso.
Si hacemos un import this en la consola de Python, nos saldrán unos principios sobre el
desarrollo en Python descritos por el creador, llamados el Zen de Python.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
34
“The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!”
Para ver el uso del lenguaje primero vamos a describir las operaciones y tipos de datos
más usados en python.
Tipos de datos.
Numéricos: int, long, float, complex.:
Int y long Son los número enteros, int está comprendidos en ±2147483647 y
long a partir de ese valor en adelante, aunque en python automáticamente pasa
de un tipo de dato a otro si nos pasamos del valor máx/min de un int o viceversa.
Float es el número decimal, representados con un punto.
Complex son los números complejos definidos con su parte real e imaginaria.
boolean, el clásico valor lógico True o False.
str, un carácter o cadena de caracteres, se representan mediante ‘ ‘.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
35
list:
Como su propio nombre indica es una lista de elementos indexados, estos
elementos pueden ser de distinto tipo y se representan mediante [ ], [1,2,3].
set:
Es lo que se conoce en matemáticas como un conjunto de elementos no
repetidos sin orden, estos se representan mediante ( ), (1,2,3,4).
tuple:
Es una lista de elementos indexados pero inmutables, quiere decir que no
podemos modificar su valor. Al igual que las listas puede contener elementos de
varios tipos.
dict:
O diccionario es la clásica estructura de datos par clave-valor, donde la clave es
única para cada valor, y los valores pueden ser elementos de cualquier tipo.
None,
Es un tipo de dato que dice que no tiene nada pero que existe la variable.
Podemos utilizarlo para inicializar una variable o para tratar este tipo con alguna
excepción.
Todos estos datos son considerados objetos de primer orden por lo que, más que un
tipo de dato, son objetos que tienen muchos métodos propios y comunes. Además la
mayoría de ellos son iterables, esto quiere decir que podemos iterar sobre el objeto
mismo y obtener sus valores directamente, los únicos que no lo son int, float y set.
Una de las causas que ha hecho que se haya puesto tan de moda el lenguaje entre la
comunidad científica es la simplicidad del código para hacer cosas que con otros
lenguajes usados para el cálculo científico y de alto rendimiento como C, C++ y Fortran
se hacen de forma más tediosa.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
36
A continuación vamos a ver algunas de las operaciones que podemos hacer en Python.
Dada su abundancia en funciones vamos a ver las más comunes a la hora de usar el
lenguaje y algunas de especial interés para el desarrollo del trabajo.
Todas las funciones descritas están basadas en la versión 2.7 de Python, por ser la que
se ha utilizado en el trabajo. Actualmente la última versión estable es la 3.4 donde hay
diferencias con algunas funciones [8].
Todos los ejemplos que se describen a continuación para hacer una introducción al
lenguaje han sido desarrollados por el autor del presente trabajo, además se
encuentran subidos en el repositorio donde se ha desarrollado el trabajo [9].
Operaciones con tipos númericos: int,long y float:
1. """Podemos definir una variable con el constructor vacío""" 2. n=int() 3. f=float() 4. print n,f
5. 0 0.0 6. 7. """O directamente dándole un valor tomando automáticamente el tipo oportuno se
gún el valor.""" 8. n=100 9. f=9.6 10. print n,f 11. 100 9.6
Operaciones con listas:
1. """podemos definir una variable con el constructor vacío""" 2. l=list() 3. """O directamente le damos valores a la lista""" 4. l=[1,2,3,4,5,6,7,8] 5. print l 6. [1, 2, 3, 4, 5, 6, 7, 8] 7. 8. """Para acceder a un elemento de la lista podemos llamarlo por su índice""" 9. l[0] # -> 1 10. l[0] = 3 #Modificamos el valor de índice 0 a 3, l[0] -> 3 11. print l[:2] 12. [3, 2] 13. print l[2:6] 14. [3, 4, 5, 6] 15. print l[2:] 16. [3, 4, 5, 6, 7, 8] 17. 18. """Métodos""" 19. l.append(3) #Inserta al final de la lista el elemento 3 20. print l 21. [3, 2, 3, 4, 5, 6, 7, 8, 3] 22. l.extend([1,2,3]) #Añade la lista que se pasa como parámetro al final 23. print l 24. [3, 2, 3, 4, 5, 6, 7, 8, 3, 1, 2, 3] 25. print l.index(2) #Devuelve el índice del primer elemento de la lista que coinc
ida con el del parámetro 26. 1
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
37
27. l.reverse() #Da la vuelta a la lista 28. print l 29. [3, 2, 1, 3, 8, 7, 6, 5, 4, 3, 2, 3] 30. l.sort() #Ordena la lista según el valor de sus elementos 31. print l 32. [1, 2, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8]
Operaciones con conjuntos:
1. """podemos definir una variable con el constructor vacío""" 2. s=set() 3. """O con set(iterable)""" 4. s=set([1,2,3]) 5. print s 6. set([1, 2, 3]) 7. 8. s.add((1,2))#añadimos el elemento (1,2) 9. s.add((4,5))#añadimos el elemento (4,5) 10. print s 11. set([(1, 2), 1, 2, 3, (4, 5)]) 12. 13. s.remove(2) #eliminamos el elemento 2 14. print s 15. set([(1, 2), 1, 3, (4, 5)]) 16. 17. print len(s) #Número de elementos del conjunto/ cardinalidad 18. 4 19. print (1,2) in s #Pregunta si el elemento x está en s 20. True 21. print (4,5) not in s #Pregunta si el elemento x no está en s 22. False 23. 24. r=set([1,2,(1,2),9,10,(9,8)]) 25. print s.union(r) #Unión de s con r 26. set([(1, 2), 1, 2, 3, (4, 5), 9, (9, 8), 10]) 27. 28. print s.intersection(r)#Intersección de s con t 29. set([(1, 2), 1]) 30. 31. print s.difference(r) #Diferencia de s y t 32. set([(4, 5), 3]) 33. 34. v=s.copy()#Crea un copia del objeto conjunto s en la variable v. 35. print v 36. set([(1, 2), 1, 3, (4, 5)]) 37.
Operaciones con tuplas:
1. """podemos definir una variable con el constructor vacío""" 2. t=tuple() 3. """O directamente la damos valores""" 4. t=(8,4,5,6,[1,3],'b') 5. print t 6. (8, 4, 5, 6, [1, 3], 'b') 7. 8. """Las tuplas no tiene método append para añadir elementos
o método extend para extender la tupla, son un tipo de objeto inmutable.No podemos modificar su contenido, tan sólo ver si un elemento está contenido, acceder por su indice e iterar sobre ellas"""
9. print t[1] #Elemento del índice 1 10. 4
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
38
11. print [1,3] in t 12. True 13. t[1]=5 14. TypeError: 'tuple' object does not support item assignment 15. 16. """Iterar sobre tuplas""" 17. for i in t: 18. print i 19. 8 | 4 | 5 | 6 | [1,3] | 'b'
Diccionarios:
1. """Es la estructura clave-valor, donde las claves son únicas, es decir, no puede haber claves repetidas
2. y los valores pueden ser 1 o varios valores""" 3. 4. d=dict() 5. d={'jack': 4098, 'sape': 4139} 6. print d 7. {'sape': 4139, 'jack': 4098} 8. 9. f=dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) 10. print f 11. {'sape': 4139, 'jack': 4098, 'guido': 4127} 12. 13. d['jack'] #accedemos por la clave 14. #A continuación vamos a ver si una cadena de texto se encuentra como clave en
algún diccionairo, por una parte vamos a pregunantar si ‘sape’ se encuentra como clave en el diccionario f ypor la otra parte vamos a hacer lo mismo pero con el diccionario d.
15. ('sape' in f) or ( d.has_key('sape')) 16. f['hola']=320 #añadimos un nuevo elemento de clave 'hola' valor 320 17. print f 18. {'sape': 4139, 'jack': 4098, 'hola': 320, 'guido': 4127} 19. 20. print f.items() #Devuelve una lista con tuplas (clave,valor) 21. [('sape', 4139), ('jack', 4098), ('hola', 320), ('guido', 4127)] 22. print f.values() #Devuelve una lista con los valores 23. [4139, 4098, 320, 4127] 24.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
39
A continuación vamos a ver algunas funciones que nos pueden resultar de ayuda en diferentes ocasiones y nos pueden ahorrar tiempo a la hora de desarrollar. Vamos a comenzar con algunas técnicas iterativas, todas ellas se pueden aplicar sobre cualquier objeto que sea iterable.
1. l=[1,2,3,4,5] 2. """La función range(x), nos devuelve una lista de valores de tamaño x, desde 0
hasta x-1 o range(x,y) entre x e y-1""" 3. d=range(1,6) 4. print l,d 5. [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] 6. 7. l=list() 8. for x in range(10): 9. l.append(x) 10. print l 11. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 12. 13. l=range(1,10) 14. """La función enumerate nos devuelve el elemento y el índice del elemento""" 15. for i,e in enumerate(l): 16. print i,e 17. 18. 0 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 19. 20. l=range(1,10) #[ 1, 2, 3, 4, 5, 6, 7, 8, 9] 21. s=range(11,20) #[11, 12, 13, 14, 15, 16, 17, 18, 19] 22. r=range(21,30) 23. """La función zip(s,l) nos devuelve elementos a pares,ternas... del mismo índi
ce comenzando por el índice 1""" 24. for i,j in zip(l,s): 25. print i,j 26. 1 11 | 2 12 | 3 13 | 4 14 | 5 15 | 6 16 | 7 17 | 8 18 | 9 19 27. 28. for i,j,x in zip(l,s,r): 29. print i,j,x 30. 1 11 21 | 2 12 22 | 3 13 23 | 4 14 24 | 5 15 25 | 6 16 26 | 7 17 27 |
8 18 28 | 9 19 29 31. 32. l=[(1,2),(4,5),(7,8)] 33. """Si nuestra lista está compuesta por pares de números podemos iterar
directamente por los valores de las parejas de números 34. for x,y in l: 35. print x,y 36. 1,2 | 4,5 | 7,8 37. 38. """Iteramos sobre un diccionario""" 39. for i in d: #Itera sobre las claves 40. print i,d[i] 41. sape 4139 42. jack 4098 43.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
40
Dado que la filosofía de Python es hacer lo más posible en una línea de código pero que sea elegante y fácilmente legible, para la generación de listas existen las listas por compresión:
1. #Obtenemos una lista del 0 al 9 2. l=[x for x in range(10)] 3. print l 4. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 5. 6. #Filtramos por los elementos pares del 0 al 9 7. l=[x for x in range(10) if x%2==0] 8. print l 9. [0, 2, 4, 6, 8] 10. 11. #Multiplicamos por 2 los elementos pares del 0 al 9 12. l=[x*2 for x in range(10) if x%2] 13. print l 14. [2, 6, 10, 14, 18] 15. 16. x=[3,6,8,2,5] 17. y=[1,4,6,5,1] 18. #Obtenemos todas la combinaciones de dos elementos de las listas x e y 19. l=[(i,j) for i in x for j in y ] 20. print l 21. [(3, 1), (3, 4), (3, 6), (3, 5), (3, 1), (6, 1), (6, 4), (6, 6), (6, 5), (6, 1), (
8, 1), (8, 4), (8, 6), (8, 5), (8, 1), (2, 1), (2, 4), (2, 6), (2, 5), (2, 1), (5, 1), (5, 4), (5, 6), (5, 5), (5, 1)]
22. 23. x=[3,6,8,2,5] 24. y=[1,4,6,5,1] 25. #Obtenemos pares de números por índice de las listas x e y 26. l=[(i,j) for i,j in zip(x,y)] 27. print l 28. [(3, 1), (6, 4), (8, 6), (2, 5), (5, 1)] 29. 30. matrix=[[3,6,8,2,5],[1,4,6,5,1],[1,4,5,6,7]] 31. l=[[row[k] for row in matrix] for k in range(5)] 32. print l 33. [[3, 1, 1], [6, 4, 4], [8, 6, 5], [2, 5, 6], [5, 1, 7]]
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
41
2.4.2. ESTUDIO SOBRE PROGRAMACIÓN PARALELA EN EL LENGUAJE PYTHON
Puesto que uno de los objetivos del trabajo final de grado es aplicar técnicas de
programación paralela para optimizar los tiempos de ejecución, procedí a hacer un
estudio sobre el estado de la cuestión en el lenguaje Python.
Comenzando primero por investigar la paralelización en sistemas de memoria
compartida, sabemos que para lenguajes de programación como Fortran, C y C++ existe
el framework OpenMP [3], el cual nos facilita mucho la tarea a la hora de poder
paralelizar bucles y en el manejo de hilos con variables compartidas y privadas.
Una vez leída documentación acerca de este tema nos encontramos con dos librerías y
un problema, las dos librería son propias del lenguaje Python y se llaman thread y
multiprocessing.
Módulo Thread
La primera, thread, sirve para el manejo de hilos de Python y la segunda,
multiprocessing, sirve para el manejo de procesos de Python, sabiendo sólo esto
podemos decir que ya tenemos solucionado el tema acerca de la programación paralela
en sistemas de memoria compartida, pero no es así.
A continuación nos encontramos con un problema y es que de la forma en la que está
construido Python no nos permite ejecutar más de un hilo a la vez en un intérprete de
Python. La causa es un mecanismo llamado Global Interpreter Lock o GIL [10] mostrado
en la Figura 17. Esto no significa que a la hora de crearnos hilos de trabajo en Python no
tengamos que declararnos variables privadas y compartidas, ya que si tenemos varios
hilos que van a modificar un mismo objeto Python debemos hacerlo.
FIGURA 17. Acción del mecanismo GIL donde podemos ver cómo no es posible ejecutar más de un thread al
mismo tiempo, extraída de [11].
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
42
Esto significa que cada hilo tiene que esperar a que el GIL sea liberado para poder
ejecutarse, por lo que no podemos ejecutar varios hilos a la vez como podemos ver en
la imagen anterior.
El tipo de bloqueo que usa es un semáforo binario construido desde un pthreads mutex
y una variable de condición. La intención del GIL, según el creador del lenguaje, es
proteger la memoria del intérprete.
Este ha sido un tema muy discutido en la comunidad Python, ya que ha habido varias
personas que han modificado el intérprete y han eliminado el GIL, pero el lenguaje se
veía afectado en temas de estabilidad y rendimiento.
Con lo que para temas de paralelización de hilos no nos serviría el módulo thread de
Python, ni el lenguaje en sí.
Entre C y Python, Cython.
Pero esto no es todo lo que nos ofrece la comunidad Python, ya que su uso fue en
aumento en comunidades matemáticas y científicas encontraron la necesidad de
acelerar los cálculos. Por lo que se desarrolló un marco de trabajo entre el lenguaje C y
Python llamado Cython [12]. Este nos permite escribir código con una sintaxis parecida
a Python y algunas características de C para luego compilarlo como si fuera código en C
y poder utilizarlo desde el intérprete de Python.
Cython nos permite llamadas o importaciones de librerías propias de C, como por
ejemplo OpenMP [3]. Al mismo tiempo nos deja a elección del usuario decidir si llevar o
no un control de hilos de ejecución en paralelo con un simple noGIL= True en las partes
del código donde queramos que el GIL no intervenga en la ejecución de hilos, pudiendo
utilizar las funciones propias de OpenMP. A continuación tan sólo nos haría falta
compilar nuestro código y hacer uso de él como si de un módulo de Python se tratase,
pero en el fondo lo tenemos compilado cómo si fuera C.
Muchas de los frameworks de Python dedicados al uso científico como son: Numpy,
SciPy, Matplotlib, Pandas, IPython, Sympy [13] y Sage [14] entre los más importantes;
tienen las funciones más complejas de ejecutar computacionalmente hablando
desarrolladas en Cython para acelerar dichos cálculos.
Tan sólo nos quedaría escribir un módulo con la sintaxis correspondiente en C, tratar los
hilos como queramos y con la librería Cython [13], compilarlo para que el intérprete de
Python lo entendiese.
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
43
Un ejemplo de código en Cython que multiplica un vector por un escalar:
1. from cython.parallel import * 2. def func(np.ndarray[double] x, double alpha): 3. cdef Py_ssize_t 4. with nogil: 5. for i in prange(x.shape[0]): 6. x[i] = alpha * x[i]
Módulo Multiprocessing.
El otro módulo que nos queda es el multiprocessing, el cual nos permite ejecutar varios
procesos a la vez cada uno en diferentes intérpretes por lo que ya no estaríamos
compartiendo memoria. La forma de compartir información entre ellos es mediante una
cola de mensajes. Además la forma de hacerlo en Python es muy sencillo, con lo cual
tenemos una forma de ejecutar procesos en paralelo e intercambiar mensajes entre
ellos, aunque esta manera no sea tan eficiente como la compartición de memoria.
Esto nos recuerda a la forma de intercambio de información en sistemas de memoria
distribuida usando MPI.
A esto se nos suma otro problema, y es que en el tipo de arquitectura en la que vamos
a lanzar la aplicación, un cluster con varios nodos, la comunicación entre procesos de
diferentes máquinas con el módulo multiprocessing no la podemos hacer. Por lo que
por último fui a investigar qué tecnologías de paso de mensajes, MPI, podemos
encontrar en el lenguaje Python.
De entre todas las que encontré, me centré en cuatro, por ser las que mejor
documentadas estaban, mejores comentarios de la comunidad de desarrollo tenían y en
un estudio de un alumno de master de la Universidad de Oslo: WENJING LIN – A
comparison of existing python modules of MPI [15].
PyPar
Proyecto de la Universidad Nacional de Australia.
https://code.google.com/p/pypar/
pyMPI
Proyecto hecho por investigadores del
Lawrence Livermore National Laboratory , California
http://pympi.sourceforge.net/index.html
https://code.google.com/p/pypar/https://code.google.com/p/pypar/http://pympi.sourceforge.net/index.htmlhttp://pympi.sourceforge.net/index.html
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
44
MPI4Py
Proyecto de Lisandro Dalcin, basado en MPI-1/2
Implementa la mayoría de funciones de MPI
https://bitbucket.org/mpi4py/mpi4py/
SciPy.MPI:
Proyecto de código libre, muy valorado en la comunidad python por su
uso matemático, científico e ingenieril.
http://scipy.org/
Puedo decir que cada una de ellas son wrappers, es decir hacen llamadas a las funciones originales de C de los estándares de MPI definidos, por lo que para el uso de ellos con Python nos hará falta tener instalada alguna versión de MPI en C. En mi caso he utilizado OpenMPI 1.5.8.
PyPar MPI4Py pyMPIP SciPy.MPI
MPI_Send
MPI_Recv
MPI_Sendrecv
MPI_Isend
MPI_Irecv
MPI_Bcast
MPI_Reduce
MPI_Allreduce
MPI_Gather
MPI_Allgather
MPI_Scatter
MPI_Alltoall
TABLA 3. [14] FUNCIONES IMPLEMENTADAS DE LOS DIFERENTES MÓDULOS DE MPI EN PYTHON [15]
https://bitbucket.org/mpi4py/mpi4py/http://scipy.org/
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
45
C PyPar MPI4Py pyMPIP SciPy.MPI
Latency 8 25 14 133 23
Bandwith 967.004 898.949 944.475 150.901 508.972
TABLA 4. [14]
LATENCIA Y BANDO DE ANCHA DE LOS DIFRENTES MÓDULOS MPI PARA PYTHON [15]
Como podemos ver en las tablas 3 y 4, de entre todas ellas MPI4Py es la que tiene
implementadas la mayoría de funciones y la que mejor latencia y ancho de banda nos
da después del uso de MPI con el lenguaje C. A continuación en la figura 18 se visualizan
algunas de las funciones de comunicación del estándar MPI.
FIGURA 18.
Visualización de algunas funciones de MPI, extraída de [16].
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
46
A continuación vamos a describir cómo usar MPI4Py, ya que sabiendo los estándares de
MPI el uso de él en Python es directo, e incluso más fácil.
A diferencia de MPI en C, MPI4Py está oriento al envío de objetos Python en mensaje,
esto quiere decir que nos permitirá enviar cualquier objeto, sin especificar el tipo de
dato, ni el tamaño y nosotros seremos los encargados de tratarlo según la situación.
Para empezar, tenemos que importar los módulos que vayamos a utilizar. En nuestro
caso necesitamos el módulo MPI del framework MPI4Py, y a continuación
inicializaremos el comunicador principal y guardaremos el id/rango del proceso y el
número total de procesos ejecutados.
Estos ejemplos están obtenidos de la documentación de MPI4PY [17].
1. from mpi4py import MPI 2. 3. """Iniciamos el comunicador""" 4. COMM=MPI.COMM_WORLD 5. 6. """Obtenemos el númerto total de procesos""" 7. SIZE=COMM.Get_size() #Get number of proceses 8. 9. """Obtenemos el id del propio processo""" 10. RANK=COMM.Get_rank() #Get id of own process
Y a partir de ahora utilizaremos las funciones de MPI según nuestro objetivo, todas ellas las llamaremos desde el comunicador MPI.COMM_WORLD. Algunas de las funciones más comunes a utilizar son: send(), rcv(), bcast(), scatter(), gather(), allgather(), alltoall(), barrier. En la documentación tenemos muchas más. A continuación algunos ejemplos de uso:
Ejemplo de envío/recepción de mensaje entre dos procesos:
1. if rank == 0: 2. data = {'a': 7, 'b': 3.14} 3. """Enviamos al proceso de id 1, el objeto data""" 4. comm.send(data, dest=1, tag=11) 5. elif rank == 1: 6. """Recibimos los datos del proceso id 0""" 7. data = comm.recv(source=0, tag=11)
-
2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones
47
Ejemplo de mensaje de broadcast:
1. if rank == 0: 2. data = {'key1' : [7, 2.72, 2+3j], 3. 'key2' : ( 'abc', 'xyz')} 4. else: 5. data = None 6. """Automáticamente si es el proceso id 0 envia el mensaje de broadcast con la
variable data a todos los procesos en ejecución y automáticamente el resto de procesos reciben el mensaje en la variable data"""
7. data = comm.bcast(data, root=0)
Ejemplo de mensaje Scatter:
1. if rank == 0: 2. data = [(i+1)**2 for i in range(size)] 3. else: 4. data = None 5. """Automáticamente si es el proceso id 0 envía el mensaje con el elemento de
indice 0 al proceso 1, indice 1 al proceso 1… y cada proceso recibe su mensaje en la variable data"""
6. data = comm.scatter(data, root=0) 7. assert data == (rank+1)**2
-
3. Implementación del Algoritmo Genético
48
3. IMPLEMENTACIÓN DEL ALGORITMO GENÉTICO
Otro de los objetivo de este trabajo es el desarrollo de una aplicación para determinar
la dinámica evolutiva de redes de regulación genética mediante la implementación en
paralelo, sobre una plataforma de computación de altas prestaciones, de un método
basado en un algoritmo genético, introducido por D. Aguilar-Hidalgo et. al. [1]. A
continuación vamos a describir como se ha llevado a cabo esta implementación
Para poder simular esta evolución de redes de regulación genética se ha implementado
en el lenguaje Python el algoritmo genético, en el que tenemos un estado inicial de la
red y un estado final, y queremos encontrar qué conjunto de reglas hacen pasar del
estado inicial al final y además si se producen nuevos enlaces entre nodos. A
continuación se describe más detalladamente la modelización del problema y las partes
que componen el algoritmo genético.
3.1. PROCESO SECUENCIAL
Como se ha explicado anteriormente en la sección 2.1 una red de regulación génica la
podemos expresar como un grafo o matriz booleana, donde sus nodos pueden tener
valores 1 ó 0 e indican si el nodo está activado o inactivo y sus enlaces pueden tener
valores -1, 0, 1 e indican la relación del link entre dos nodos cómo: -1 si es un enlace
represor, 0 si no hay enlace y 1 si es un enlace activador. Veamos un ejemplo, donde las
flechas rojas son enlaces inhibidores y las flechas verdes enlaces activadores.
(
0 0 1 1 0−1 0 0 0 10 −1 0 0 00 0 1 0 00 0 −1 0 0)
FIGURA 19.
Representación matricial de una red booleana, donde el color de los nodos rojo significa que se
encuentra en estado desactivado y el color verde activado. Igualmente los enlaces de color verde
tienen un carácter activador y los rojos inhibidores.
En la figura 19 podemos ver que el estado de la red asociado a los diferentes nodos será:
(0 0 0 1 1) donde cada posición corresponde a cada uno de los nodos.
Como se describió en la sección 2.1 sobre modelos de redes génicas, el estado de los
nodos en cada intervalo de tiempo vendrá dado por una expresión booleana la cual
1 2
3
4 5
-
3. Implementación del Algoritmo Genético
49
depende de los enlaces de entrada sobre el nodo. Los enlaces los encontramos de dos
tipos, activador e inhibidores y los estados de los nodos pueden ser activos o inactivos,
por lo que la expresión booleana podemos definirla por una serie de reglas que según el
número y tipo de enlaces evolucionarán de una forma u otra. A continuación se
describen cada una de las reglas de evolución de la red:
1. Regla de la mayoría
Cuando en un nodo actúan enlaces activadores y represores, el estado final del
nodo lo determinará el tipo que tenga mayor número de enlaces.
2. Regla del represor absoluto
Cuando en un nodo basta con que haya un solo enlace represor, el nodo pasará
a estar desactivado.
3. Acción conjunta de dos activadores
Cuando en un nodo basta con que actúen dos o más enlaces activadores, el nodo
pasará a un estado activo.
4. Acción conjunta de dos represores
Cuando en un nodo basta con que actúen dos o más enlaces represores, el nodo
pasará a un estado desactivado.
-
3. Implementación del Algoritmo Genético
50
Ahora vamos a ver cómo describimos el problema en términos del algoritmo genético y
como incluimos la aplicación de estas reglas. Anteriormente en la sección 2.2 ya
explicamos los componentes de un algoritmo genético, así pues vamos a describir el
objetivo de la aplicación del algoritmo genético a estas redes booleanas que modelan
redes génicas.
Este objetivo es encontrar qué reglas hacen pasar de un estado inicial A, a un estado
final B a través de alguna de las soluciones que aporta el algoritmo genético, estado B*.
Además si por casualidad en el estado B* aparecen enlaces nuevos entre nodos, ver si
tiene sentido biológico. Figura 20.
FIGURA 20.
Representa el objetivo del algoritmo genético, donde a través de un estado inicial A y un estado
final B se intenta encontrar un estado B* que haga pasar del estado inicial A al estado final B.
1
5
3
2
4
1
5
3 4
2
Estado Inicial A.
1
5
3
2
4
Estado Final B.
Estado B*.
-
3. Implementación del Algoritmo Genético
51
Genes, en nuestro problema tendremos dos tipos de genes, los valores de los enlaces
con posibles valores entre-1, 0,1 y los números de las reglas con posibles valores entre
1, 2, 3,4.
Individuos, estos están compuesto de dos partes, la primera parte de enlaces y la
segunda de reglas. La parte de enlaces no es más que nuestra matriz booleana que
representa al grafo pero puesta en fila, una a continuación de otra. La parte de las reglas
serán las reglas que determinan la evolución de este individuo de la red. Y la parte de
los estados de los nodos de la red es una fila con los estados de cada nodo.
FIGURA 21.
Estructura de los individuos del algoritmo genético desarrollado, representan la red booleana.
Compuestos de dos partes, la primera de enlaces y la segunda las reglas que determinan los
estados de los nodos.
Así pues nuestro individuo será una lista de tamaño 1056 elementos, ya que en nuestra
red hay 32 nodos y necesitamos expresar la matriz de adyacencia de los nodos más la
regla activada en cada nodo, por lo que 32 ∗ 32 = 1024; 1024 + 32 = 1056. En donde
hasta la posición 1024 está definida la matriz de enlaces de nodos de la red genética y a
partir de la posición 1024 están las diferentes reglas asociadas a cada nodo que deciden
como va a evolucionar esta red.
A su vez cada individuo tiene asociada otra lista de 32 elementos o número de nodos de
la red que se corresponde con el estado de los nodos de la red, es decir si el nodo i se
encuentra activado, 1, o desactivado, 0, debido a los enlaces y reglas,
La función de evaluación la podemos definir en dos partes, la primera como las
diferencias en enlaces que hay entre nuestro individuo actual y la red final. Y la segunda
como las diferencias que hay en los estados de los nodos de la red actual y la red final.
Además cada término queda multiplicado por un valor ƛ y ƛ-1 Quedando formulado de
esta forma:
𝑓 = 𝜆𝑑𝑖𝑠𝑡[𝐵, 𝐵∗]
max (𝑑𝑖𝑠𝑡[𝐵, 𝐵∗])+ (1 − 𝜆)
𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗]
max (𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗])
FUNCIÓN 1.
Función de evaluación de los individuos del algoritmo genético, determina la bondad de cada
solución encontrada.
-
3. Implementación del Algoritmo Genético
52
Siendo B y B* el estado de los nodos de la red del individuo actual y la red final
respectivamente, por lo que 𝑑𝑖𝑠𝑡[𝐵, 𝐵∗] es la diferencia de los estados de los nodos del
individuo actual y la red final. Y max (𝑑𝑖𝑠𝑡[𝐵, 𝐵∗]) la máxima diferencia que puede haber
entre el estado de los nodos del individuo actual y la red final.
Por otra parte tenemos 𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗] que son las diferencias en enlaces que hay
entre la red del individuo actual y la red final. Y max (𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗]) la máxima
diferencia que puede haber entre enlaces de la red del individuo actual y la red final.
La generación de la población inicial la hacemos con permutaciones sobre la red inicial,
es decir dado el estado inicial A de la red, su lista de adyacencia, generamos una nueva
lista con permutaciones aleatorias de los enlaces, con lo que obtenemos un nuevo
individuo. Con esto conseguimos no añadir más número de enlaces en la red, pero sí
modificar los enlaces entre los diferentes nodos. Para los siguientes individuos, hacemos
permutaciones sobre el anteriormente creado en vez de sobre la red inicial A, con esto
conseguimos la suficiente divergencia de soluciones, pero sin alejarnos demasiado del
estado inicial A. Figura 22.
FIGURA 22.
Esquema de generación de la población inicial a partir del estado inicial A.
Y la parte de reglas la generamos aleatoriamente para cada individuo, las reglas las
definimos con los número del 1 al 4, por lo que obtenemos un número al azar para cada
nodo.
Estado
Inicial A Permutamos
enlaces de la
red
Nuevo individuo
Añadimos a la población
inicial
Pérmutamos anterior
individuo creado.
-
3. Implementación del Algoritmo Genético
53
Por otra parte el estado de los nodos de la red para cada individuo generado, es decir si
los nodos están activados o desactivados, lo obtenemos de la red inicial A. Podemos
decir que todos los individuos de la población inicial van a tener el mismo estado de
nodos que la estado inicial A.
Además, la población inicial la generaremos con un tamaño de 100 individuos.
El operador entrecruzamiento lo definimos eligiendo un pivote al azar en la parte de los
enlaces e intercambiando las partes. Una vez se ha hecho el intercambio comprobamos
que la conectividad de estos individuos, en términos de enlaces, se mantiene en el rango
10%−+ de la red final y si está en el rango procedemos a la parte de reglas. Esta parte
también viene marcada por un puntero en la parte de las reglas dado por expresión 2:
𝐾 = 𝑖𝑛𝑡 (𝑙
𝑁) + 1
EXPRESIÓN 2.
Expresión que nos calcula el punto de entrecruzamiento de dos individuos en la parte de los
enlaces.
Siendo K el puntero que marca la parte de reglas que se intercambian, l es el puntero
usado anteriormente para la parte de los enlaces y N el número total de nodos de la
red. A continuación lo mostramos en la Figura 23.
FIGURA 23. OPERADOR ENTRECRUZAMIENTO
Muestra cómo funciona el operador entrecruzamiento entre dos individuos.
La elección de padres para el crossover la hacemos por el método de la ruleta, lo que
quiere decir que la probabilidad de que un individuo sea elegido como padre es
directamente proporcional a su valor de función de evaluación.
Puntero 𝑙,
parte de
enlaces
Puntero 𝑘,
parte de las
reglas
-
3. Implementación del Algoritmo Genético
54
El operador mutación, lo definimos con dos probabilidades, una para la parte de enlaces
𝜇𝑙 y otra para la de reglas 𝜇𝑟 . Iteramos sobre cada elemento del individuo y por cada
elemento obtenemos un número aleatorio entre 0.0 y 1.0, si ha superado la probabilidad
correspondiente mutamos ese valor a los otros restantes, siendo para los enlaces 1/2 la
probabilidad para cada valor, ya que sólo quedan dos posibles valores de enlaces. Y para
las reglas tenemos 1/3 para cada valor, ya que sólo quedan tres valores posibles de
reglas. Por último si no hubiésemos superamos la probabilidad de mutación pasamos al
siguiente elemento de la lista.
Después de hacer un estudio y documentarnos [1] [18] [19] sobre las redes génicas en
hemos elegido los siguientes valores para la probabilidad de mutación, ya que son los
que mejor imitan el comportamiento de estas cómo: 𝜇𝑙 = 0.001 y s 𝜇𝑟 = 0.5 .
A continuación pasamos a la parte de ejecución del algoritmo, donde vamos a poder dar
como parámetros varias variables:
Número de nodos de la red
Probabilidad de mutación en links
Probabilidad de mutación en reglas
Lambda para función de evaluación
Red inicial, enlaces y estados de los nodos
Red final, enlaces y estados de los nodos
Número de individuos de la población
Número de generaciones a evolucionar
-
3. Implementación del Algoritmo Genético
55
Vamos a ver en un diagrama de flujo, figura 24, cómo sería la ejecución del algoritmo
añadiendo la aplicación de reglas en el algoritmo genético.
FIGURA 24. ALGORITMO GENÉTICO IMPLEMENTADO
Indica el diagrama de flujo del algoritmo genético desarrollado.
3.1 Seleccionamos a padres
3.2 Aplicamos el
operador Crossover
3.3 Aplicamos el
operador Mutación
3.4 Aplicamos las reglas definidas
3.5 Evaluamos
la población
3.0 Repetimos el proceso N generaciones
1.0 Generamos Población Inicial
2.0 Evaluamos la población inicial
-
3. Implementación del Algoritmo Genético
56
3.2. PROCESO PARALELO
En la sección 2.4 ya describimos los posibles modelos paralelos de un algoritmo
genético, recordándolo teníamos dos modelos. El modelo en Islas y el modelo celular,
donde el primero está más orientado a un sistema de memoria distribuida, donde ca