Compare commits

..

6 Commits

6 changed files with 182 additions and 41 deletions

0
docs/.gitkeep Normal file
View File

143
docs/Summary.org Normal file
View File

@@ -0,0 +1,143 @@
#+TITLE: Práctica 2
#+SUBTITLE: Metaheurísticas
#+AUTHOR: Amin Kasrou Aouam
#+DATE: 2021-06-22
#+PANDOC_OPTIONS: template:~/.pandoc/templates/eisvogel.latex
#+PANDOC_OPTIONS: listings:t
#+PANDOC_OPTIONS: toc:t
#+PANDOC_METADATA: lang=es
#+PANDOC_METADATA: titlepage:t
#+PANDOC_METADATA: listings-no-page-break:t
#+PANDOC_METADATA: toc-own-page:t
#+PANDOC_METADATA: table-use-row-colors:t
#+PANDOC_METADATA: colorlinks:t
#+PANDOC_METADATA: logo:/home/coolneng/Photos/Logos/UGR.png
#+LaTeX_HEADER: \usepackage[ruled, lined, linesnumbered, commentsnumbered, longend]{algorithm2e}
* Práctica 2
** Introducción
En esta práctica, usaremos distintos algoritmos de búsqueda, basados en poblaciones, para resolver el problema de la máxima diversidad (MDP). Implementaremos:
- Algoritmo genético
- Algoritmo memético
** Algoritmos
*** Genético
Los algoritmos genéticos se inspiran en la evolución natural y la genética. Generan un conjunto de soluciones inicial (i.e. población), seleccionan un subconjunto de individuos sobre los cuales se opera, hacen operaciones de recombinación y mutación, y finalmente reemplazan la población anterior por una nueva.
El procedimiento general del algoritmo queda ilustrado a continuación:
\begin{algorithm}
\KwIn{A list $[a_i]$, $i=1, 2, \cdots, n$, that contains the population of individuals}
\KwOut{Processed list}
$P(t) \leftarrow initializePopulation()$
$P(t) \leftarrow evaluatePopulation()$
\While{$\neg stop condition $}{
$t = t + 1$
$parents \leftarrow selectParents(P(t-1))$
$offspring \leftarrow recombine(parents)$
$offspring \leftarrow mutate(offspring)$
$P(t) \leftarrow replacePopulation(P(t-1), offspring)$
$P(t) \leftarrow evaluatePopulation()$
}
\KwRet{$P(t)$}
\end{algorithm}
Procedemos a la implementación de 4 variantes distintas, según 2 criterios:
**** Criterio de reemplazamiento
- *Generacional*: la nueva población reemplaza totalmente a la población anterior
- *Estacionario*: los dos mejores hijos reemplazan los dos peores individuos en la población anterior
**** Operador de cruce
- *Uniforme*: mantiene las posiciones comunes de ambos padres, las demás se eligen de forma aleatoria de cada padre (requiere reparador)
- *Posición*: mantiene las posiciones comunes de ambos padres, elige el resto de elementos de cada padre y los baraja. Genera 2 hijos.
*** Memético
Los algoritmos meméticos surgen de la hibridación de un algoritmo genético, con un algoritmo de búsqueda local. El resultado es un algoritmo que posee un buen equilibrio entre exploración y explotación.
El procedimiento general del algoritmo queda ilustrado a continuación:
\begin{algorithm}
\KwIn{A list $[a_i]$, $i=1, 2, \cdots, n$, that contains the population of individuals}
\KwOut{Processed list}
$P(t) \leftarrow initializePopulation()$
$P(t) \leftarrow evaluatePopulation()$
\While{$\neg stop condition $}{
\If{$certain iteration$}{
$P(t) <- localSearch(P(t-1))$
}
$t = t + 1$
$parents \leftarrow selectParents(P(t-1))$
$offspring \leftarrow recombine(parents)$
$offspring \leftarrow mutate(offspring)$
$P(t) \leftarrow replacePopulation(P(t-1), offspring)$
$P(t) \leftarrow evaluatePopulation()$
}
\KwRet{$P(t)$}
\end{algorithm}
Procedemos a la implementación de 3 variantes distintas:
- Búsqueda local sobre todos los cromosomas
- Búsqueda local sobre un subconjunto aleatorio de cromosomas
- Búsqueda local sobre un el subconjunto de los mejores cromosomas
** Implementación
La práctica ha sido implementada en /Python/, usando las siguientes bibliotecas:
- NumPy
- Pandas
*** Instalación
Para ejecutar el programa es preciso instalar Python, junto con las bibliotecas *Pandas* y *NumPy*.
Se proporciona el archivo shell.nix para facilitar la instalación de las dependencias, con el gestor de paquetes [[https://nixos.org/][Nix]]. Tras instalar la herramienta Nix, únicamente habría que ejecutar el siguiente comando en la raíz del proyecto:
#+begin_src shell
nix-shell
#+end_src
** Ejecución
La ejecución del programa se realiza mediante el siguiente comando:
#+begin_src shell
python src/main.py <dataset> <algoritmo> <parámetros>
#+end_src
Los parámetros posibles son:
| dataset | algoritmo | parámetros |
| Cualquier archivo de la carpeta data | genetic | uniform/position generation/stationary |
| | memetic | all/random/best |
También se proporciona un script que ejecuta 1 iteración de cada algoritmo, sobre cada uno de los /datasets/, y guarda los resultados en una hoja de cálculo. Se puede ejecutar mediante el siguiente comando:
#+begin_src shell
python src/execution.py
#+end_src
*Nota*: se precisa instalar la biblioteca [[https://xlsxwriter.readthedocs.io/][XlsxWriter]] para la exportación de los resultados a un archivo Excel.
* Análisis de los resultados
Desafortunadamente, debido a un tiempo de ejecución excesivamente alto (incluso tras ajustar los metaparámetros) no podemos proporcionar resultados de la ejecución de los algoritmos.

BIN
docs/Summary.pdf Normal file

Binary file not shown.

View File

@@ -20,8 +20,7 @@ def create_dataframes():
def process_output(results): def process_output(results):
distances = [] distances = []
time = [] time = []
for element in results: for line in results:
for line in element:
if line.startswith(bytes("Total distance:", encoding="utf-8")): if line.startswith(bytes("Total distance:", encoding="utf-8")):
line_elements = line.split(sep=bytes(":", encoding="utf-8")) line_elements = line.split(sep=bytes(":", encoding="utf-8"))
distances.append(float(line_elements[1])) distances.append(float(line_elements[1]))
@@ -31,8 +30,8 @@ def process_output(results):
return distances, time return distances, time
def populate_dataframe(df, output_list, dataset): def populate_dataframe(df, output_cmd, dataset):
distances, time = process_output(output_list) distances, time = process_output(output_cmd)
data_dict = { data_dict = {
"dataset": dataset.removeprefix("data/"), "dataset": dataset.removeprefix("data/"),
"media distancia": mean(distances), "media distancia": mean(distances),
@@ -44,7 +43,7 @@ def populate_dataframe(df, output_list, dataset):
return df return df
def script_execution(filenames, df_list, iterations=2): def script_execution(filenames, df_list):
script = "src/main.py" script = "src/main.py"
parameters = [ parameters = [
["genetic", "uniform", "generational"], ["genetic", "uniform", "generational"],
@@ -55,19 +54,15 @@ def script_execution(filenames, df_list, iterations=2):
["memetic", "random"], ["memetic", "random"],
["memetic", "best"], ["memetic", "best"],
] ]
populated_list = []
for dataset in filenames: for dataset in filenames:
print(f"Running on dataset {dataset}") print(f"Running on dataset {dataset}")
for df, params in zip(df_list, parameters): for index, params in zip(range(4), parameters):
print(f"Running {params} algorithm") print(f"Running {params} algorithm")
output_list = []
for _ in range(iterations):
output_cmd = run( output_cmd = run(
[executable, script, dataset, *params], capture_output=True [executable, script, dataset, *params], capture_output=True
).stdout.splitlines() ).stdout.splitlines()
output_list.append(output_cmd) df_list[index] = populate_dataframe(df_list[index], output_cmd, dataset)
populated_list.append(populate_dataframe(df, output_list, dataset)) return df_list
return populated_list
def export_results(df_list): def export_results(df_list):

View File

@@ -22,16 +22,6 @@ def compute_distance(element, solution, data):
return accumulator return accumulator
def get_first_random_solution(n, m, data):
solution = DataFrame(columns=["point", "distance"])
seed(42)
solution["point"] = choice(n, size=m, replace=False)
solution["distance"] = solution["point"].apply(
func=compute_distance, solution=solution, data=data
)
return solution
def element_in_dataframe(solution, element): def element_in_dataframe(solution, element):
duplicates = solution.query(f"point == {element}") duplicates = solution.query(f"point == {element}")
return not duplicates.empty return not duplicates.empty
@@ -69,6 +59,6 @@ def explore_neighbourhood(element, n, data, max_iterations=100000):
def local_search(first_solution, n, data): def local_search(first_solution, n, data):
best_solution = explore_neighbourhood( best_solution = explore_neighbourhood(
element=first_solution, n=n, data=data, max_iterations=50 element=first_solution, n=n, data=data, max_iterations=5
) )
return best_solution return best_solution

View File

@@ -13,21 +13,34 @@ def get_best_indices(n, population):
return best_elements return best_elements
def replace_elements(current_population, new_population, indices):
for item in indices:
current_population[item] = new_population[item]
return current_population
def run_local_search(n, data, population, mode, probability=0.1): def run_local_search(n, data, population, mode, probability=0.1):
new_population = [] neighbourhood = []
if mode == "all": if mode == "all":
for individual in population: for individual in population:
new_population.append(local_search(individual, n, data)) neighbourhood.append(local_search(individual, n, data))
new_population = neighbourhood
elif mode == "random": elif mode == "random":
expected_individuals = len(population) * probability expected_individuals = len(population) * probability
indices = []
for _ in range(expected_individuals): for _ in range(expected_individuals):
random_individual = population[randint(len(population))] random_index = randint(len(population))
new_population.append(local_search(random_individual, n, data)) random_individual = population[random_index]
neighbourhood.append(local_search(random_individual, n, data))
indices.append(random_index)
new_population = replace_elements(population, neighbourhood, indices)
else: else:
expected_individuals = len(population) * probability expected_individuals = len(population) * probability
best_indexes = get_best_indices(n=expected_individuals, population=population) best_indices = get_best_indices(n=expected_individuals, population=population)
for element in best_indexes: for element in best_indices:
new_population.append(local_search(population[element], n, data)) neighbourhood.append(local_search(population[element], n, data))
new_population = replace_elements(population, neighbourhood, best_indices)
return new_population
def memetic_algorithm(n, m, data, hybridation, max_iterations=100000): def memetic_algorithm(n, m, data, hybridation, max_iterations=100000):
@@ -35,8 +48,8 @@ def memetic_algorithm(n, m, data, hybridation, max_iterations=100000):
population = evaluate_population(population, data) population = evaluate_population(population, data)
for i in range(max_iterations): for i in range(max_iterations):
if i % 10 == 0: if i % 10 == 0:
best_index, _ = get_best_elements(population) population = run_local_search(n, data, population, mode=hybridation)
run_local_search(n, data, population, mode=hybridation) i += 5
parents = select_parents(population, n, mode="stationary") parents = select_parents(population, n, mode="stationary")
offspring = crossover(mode="position", parents=parents, m=m) offspring = crossover(mode="position", parents=parents, m=m)
offspring = mutate(offspring, n, data) offspring = mutate(offspring, n, data)