Skip to content

A1. Base Experiment

Ezequiel Torres Feyuk edited this page Aug 8, 2017 · 39 revisions

The following appendix explain in detail the logic behind the base experiment. The base experiment is the one deployed when a new experiment is created. It is also known as Toy Problem (toy_problem). Before starting the explanation of the experiment, a brief description is given about the interface to implement in order to create a MLC experiment:

Experiment Format

The following code snippet shows the Evaluation file of an experiment. It has three functions which are explained in detail:

1  # Helper method
2  def individual_data(indiv):
3      # TODO: Implement experiment evaluation
4      ...
5      return x, y, b
6
7
8  def cost(indiv):
9      # Evaluate individual
10     x, y, b = individual_data(indiv)
11     
12     # TODO: Calculate cost as a function of y and b
13     ...
14     return cost
15
16
17 def show_best(index, generation, indiv, cost, block=True):
18     # Evaluate individual
19     x, y, b = individual_data(indiv)
20
21     # TODO: Create a graphic with Matplotlib or Qt showing 
22     # how good the individual is
  • individual_data (optional): Method responsible of evaluating the Individual received by parameter. It is recommended to separate the evaluation of the Individual from the Cost calculation since the evaluation must be used in the graph generation too. Since this function is not directly called by MLC, the input and output can be modified by the user if necessary.

  • cost (mandatory): Method that receives an Individual and returns the Cost associated to it. This function is explicitly called by MLC, so the method input and output parameters must not be changed. To calculate the *Cost, the Individual must be evaluated first. To do this, we recommend the usage of the function individual_data.

  • show_best (mandatory): Method that receives the best Individual (lower cost) of a generation and proceed to create a graph showing the quality of the solution found. This function is explicitly called by MLC, so the method input and output parameters must not be changed. This function is executed when the button Best Individual in the Results Tab window is clicked. It is responsibility of the user to create a graph that shows how good the function found by MLC is good or not. If the user doesn't want to implement this feature, just leave the function non-implemented.

Toy Problem

The following code snippet shows the code of the base experiment. The goal of the experiment is to find a well-known mathematical function (test function) in order to test the effectiveness of MLC as a pattern matching tool. The easier way to fulfill this goal is to find the most Individual to the test function, comparing the values of both functions point to point.

1  import numpy as np
2  import MLC.Log.log as lg
3  import matplotlib.pyplot as plt
4  import random
5  import sys
6  import time
7 
8  from MLC.mlc_parameters.mlc_parameters import Config
9  from PyQt5.QtCore import Qt
10
11
12  def individual_data(indiv):
13      SAMPLES = 201
14      x = np.linspace(-10.0, 10.0, num=SAMPLES)
15      y = np.tanh(x**3 - x**2 - 1)
16
17      config = Config.get_instance()
18      artificial_noise = config.getint('EVALUATOR', 'artificialnoise')
19      y_with_noise = y + [random.random() - 0.5 for _ in xrange(SAMPLES)] +   artificial_noise * 500
20
21      if isinstance(indiv.get_formal(), str):
22          formal = indiv.get_formal().replace('S0', 'x')
23      else:
24          # toy problem support for multiple controls
25          formal = indiv.get_formal()[0].replace('S0', 'x')
26
27      # Calculate J like the sum of the square difference of the
28      # functions in every point
29      lg.logger_.debug('[POP][TOY_PROBLEM] Individual Formal: ' + formal)
30      b = indiv.get_tree().calculate_expression([x])
31
32      # If the expression doesn't have the term 'x',
33      # the eval returns a value (float) instead of an array.
34      # In that case transform it to an array
35      if type(b) == float:
36          b = np.repeat(b, SAMPLES)
37
38      return x, y, y_with_noise, b
39
40
41  def cost(indiv):
42      x, y, y_with_noise, b = individual_data(indiv)
43
44      # Deactivate the numpy warnings, because this sum could raise an overflow
45      # Runtime warning from time to time
46      np.seterr(all='ignore')
47      cost_value = float(np.sum((b - y_with_noise)**2))
48      np.seterr(all='warn')
49
50      return cost_value
51
52
53  def show_best(index, generation, indiv, cost, block=True):
54      x, y, y_with_noise, b = individual_data(indiv)
55      cuadratic_error = np.sqrt((y_with_noise - b)**2 / (1 + np.absolute(x**2)))
56
57      fig = plt.figure()
58      # Put figure window on top of all other windows
59      fig.canvas.manager.window.setWindowModality(Qt.ApplicationModal)
60
61      plt.suptitle("Generation N#{0} - Individual N#{1}\n"
62                   "Cost: {2}\n Formal: {3}".format(generation,
63                                                    index,
64                                                    cost,
65                                                    indiv.get_formal()))
66      plt.subplot(2, 1, 1)
67      plt.plot(x, y, x, y_with_noise, '*', x, b)
68
69      plt.subplot(2, 1, 2)
70      plt.plot(x, cuadratic_error, '*r')
71      plt.yscale('log')
72      plt.show(block=block)

The lines 14 and 15 show the test function

En las líneas 14 y 15 se puede ver como se arma la función a buscar. La misma es una tangente hiperbólica que posee como parámetro un polinomio de tercer grado. Es interesante notar la elección de la función debido a que el MLC no posee la capacidad de generar polinomios de grado mayor a uno por defecto (Para soportar polinomios de grados mayores a uno se debe agregar como operación la potenciación (power) a través de la propiedad opsetrange). Esto quiere decir que el MLC nunca va a encontrar una función que anule a la función buscada, sólo va a poder acercarse a la misma.

Además de lo nombrado en el párrafo anterior, en la línea 19 se puede observar como se agrega una componente de ruido a la señal original generada. La idea detrás de la adición de ruido uniforme consiste en intentar engañar al MLC respecto de la verdadera función que debe buscar. De funcionar correctamente, la aplicación deberá converger a un valor que se acerque lo más posible a la señal original generada, ignorando el ruido agregado.

En la línea 30 es en donde se evalúa al Individuo. A diferencia del código original, esta versión del MLC almacena los Individuos en forma de árbol en vez de través de un simple string. Gracias a esta refactorización es mucho más sencillo obtener propiedades y operar sobre los Individuos para realizar operaciones como la simplificación de términos. La función get_tree() de la clase Individuo devuelve un árbol (clase LispTreeExpr) que permite evaluar al Individuo en cuestión a través del método calculate_expression(). Este método recibe como parámetro una lista de sensores. En este ejemplo existe un sólo sensor (S0), por lo cual al método se le pasa como argumento una lista con un sólo argumento. Dado que sólo se desea comparar dos funciones, el sensor representa el dominio de la función evaluada. Tener en cuenta que la cantidad de puntos utilizados en el dominio es totalmente arbitraria. El resultado del Individuo es almacenado en la función b (el nombre de esta variable fue elegido para respetar la nomenclatura introducida en la sección 1.3.2.)

Como se explicó anteriormente, la función individual_data no posee restricciones respecto a los parámetros recibidos ni a los parámetros que retorna. En este ejemplo, la misma retorna el dominio en el cual la función fue evaluada (x), la función buscada (y), la función buscada con ruido (y_with_noise) y el valor del Individuo evaluado (b). Con estos valores se calcula el costo como una función dependiente de la función con ruido y el Individuo evaluado, como se puede observar en la línea 47 del Code Snippet N°8.

Por último, el experimento base provee una implementación de la función show_best. La misma busca contrastar al mejor Individuo considerando la generación consultada con la función original generada (y). El gráfico es realizado a través de matplotlib y a través de la función subplot, se exhiben dos gráficos en la misma imagen. El resultado de dicha función se exhibe en la figura N°35. Se procede a explicar brevemente la información que muestran dichos gráficos:

  • La primer figura contrasta el Individuo evaluado (b) con la función original sin ruido (y). Se puede observar como la función azul (b) intenta copiar la función verde (y), aún cuando la función contra la cual evaluó fue la función con ruido, la cual se puede visualizar en la figura N°17 en los puntos de color naranja.
  • En la segunda se visualiza el error cuadrático medio entre b e y punto a punto. Se puede apreciar como el error aumenta en la parte central del gráfico, donde la diferencia entre la función verde (y) y la función azul (b) se maximiza.

Best Individual

Clone this wiki locally