Crear un agente con creatividad artificial

La creatividad artística es una de las capacidades humanas que más nos distinguen de los demás seres vivos. Es una de nuestras capacidades mas difíciles de imitar por las inteligencias artificiales. Tanto es así que hasta los más pesimistas pronósticos sobre la destrucción del empleo por parte de las máquinas salvan los empleos creativos. Pero la inteligencias artificiales poco a poco se adentran el el campo de la creatividad construyendo lo que se conoce, con el poco creativo nombre de creatividad artificial.

Es importante señalar que la creatividad artificial se centra en el proceso creativo no en el impulso creativo. Podemos imitar los resultados del proceso creativo humano pero no la necesidad, el contexto o el mensaje de la obra. Los resultados a veces son tan sorprendentes que parecen contener un mensaje o significado mas allá de la propia obra, pero es algo accidental, codificado en los datos con los que se alimentó la obra. Realmente se busca la imitación del arte humano, no la expresión de la máquina.

Elegir el dominio artístico

No es lo mismo crear un libro, que una pintura, una escultura o un vestido. Incluso dentro de cada dominio hay casos diferentes, no es lo mismo escribir una novela que un poema. Ni sigue las mismas reglas una novela romántica que una de misterio e incluso dentro del mismo género hay estilos.

Reunir ejemplos

Necesitamos recopilar obras de arte del dominio. Las necesitamos para alimentar nuestro sistema y extraer sus características y reglas. Hay que tener en cuenta que estas obras son las que definirán las creaciones de nuestro agente hay que reunir obras como las que queremos que produzca

Convertir las obras a un modelo

El modelo es una forma de representar la obra de forma “entendible y manipulable” por nuestro software.

Reglas y algoritmo generador

La creatividad necesita seguir una reglas. Si tiramos letras al azar sobre una página el resultado será muy innovador y único pero su valor como novela es muy pobre. Las reglas nos dicen como generar una obra a partir de una entrada. Pueden estar definidas a mano, aprendidas automáticamente de nuestros ejemplos o una mezcla de ambas. Podemos darnos el lujo de romper alguna regla durante el proceso creativo pero si rompemos todas obtendremos un sin sentido.

siguiente estas reglas el algoritmo puede crear una obra a partir de los datos proporcionados como entrada.

Datos de entrada o semilla

Hace falta definir una “semilla”, una entrada al modelo con la que este pueda “tirar del hilo” para generar la obra de arte. La semilla puede ser elegida por un humano, generada al azar o tomar algún valor de algún sitio. En los modelos iterativos (más adelante los vemos) la semilla incluye una obra que es la que vamos a transformar. En la primera iteración se pueden usar datos generados al azar.

Representar el modelo

La representación es la traducción de ese modelo a la obra que nosotros los humanos apreciamos. Es el paso contrario a convertir la obra en modelo

Modelos iterativos

Lo ideal seria que a la primera la obra resultante fuera valida. Pero Actualmente se aprovecha que tenemos un “juez” para convertir el proceso de creación de la obra en uno iterativo. En este proceso se toma la obra generada o mejor dicho su modelo y se usa para realimentar el algoritmo generador de forma iterativa transformando la obra intentando mejorar la puntuación del juez en cada iteración. Este proceso recuerda al de las metaheurísticas.

Juez

El juez actúa valorando la obra creada según las características deseadas. Estas características pueden ser aprendidas o definidas por nosotros. Esta parte del algoritmo se comporta como la función fitness.

Hay algoritmos generadores que en lugar de generar una obra generan varias obras que son pequeñas variaciones de la mima. El juez actúa eligiendo con cual se realimenta el proceso. Que por lo general suele ser la que mejor puntuación obtiene.

En este punto se puede introducir “ayuda” humana que actué como juez, si no en todas las iteraciones cada cierto número de ellas.

Ejemplo

En el caso del generador de textos basado cadena de Markov que vimos en esta entrada del blog tendríamos los siguientes elementos:

  • Los textos que se usan para entrenarla son los ejemplos.
  • El modelo son los tokens que se extraen del texto para entrenar a la red.
  • Los n-gramas que se aprenden son la reglas generadoras.
  • El algoritmo generador es tan sencillo como elegir al azar la palabra siguiente ponderando este azar según las probabilidades indicadas por las reglas (n-gramas)
  • En este caso no hay juez a que es un algoritmo de una única pasada

Vamos a convertir este sistema en iterativo. En lugar de una frase se generaran varias. Un humano indicará cual es la que más le gusta y a partir de esa frase se generaran nuevas frases. Para ello el algoritmo tomará un punto aleatorio de la frase, eliminará todas las palabras a partir de ese punto y continuará creando la el texto usando los n-gramas aprendidos para elegir la siguientes palabras.

Brillo y contraste. Ajuste automático.

El ajuste del brillo y del contraste se usa para mejorar la visibilidad de la imagen. El brillo añade o reduce luminosidad a la imagen y el contraste aumenta o disminuye la distancia entre los valores. No vamos a complicarnos la vida. La fórmula para ajustar el brillo y el contraste de una imagen es muy sencilla, es la unión de dos formulas. Como ya os imaginareis, la de ajuste del brillo y la de ajuste del contraste

Formula para ajustar el brillo de la imagen:

newPixelValue = pixelValue+b;

Sumando a cada pixel un valor lo hacemos más brillante, si el valor es positivo, o más oscuro, si es negativo.

Formula para ajuste del contraste:

newPixelValue = c * (pixelValue-128) + 128

Combinando ambas tenemos una formaula que nos permite ajusta ambos valores:

newPixelValue = c * (pixelValue-128) + 128 + b

Siendo el parámetro c el contraste y b el brillo. Sólo hay que tener en cuenta las diferencias entre la estructura de datos de color y de escala de grises y tenemos el código listo.

Pero como nos encanta complicarnos la vida vamos a intentar que el ajuste del brillo y el contraste sea automático. ¿Como?. Supongamos que una imagen correctamente balanceada “ocupa” todo el rango de valores de 0 a 255. Vamos a “estirar” los valores de la imagen para que el mínimo sea el 0 y el máximo el 255. Es tan “sencillo” como buscar el valor más bajo y más alto que hay en la imagen. Ahora seleccionamos los valores contraste y brillo que hacen que el mínimo valor sea 0 y el máximo 255. Suena lógico, ¿cómo lo hacemos?. ¡Resolviendo ecuaciones!. Contened vuestra emoción.

Vamos a hacer que el mínimo sea la variable low y el máximo high. Tenemos estas dos ecuaciones:

(c*(high-128))+128+b = 255

(c*(low-128))+128+b = 0

Resolvemos el valor de b en la que es igual a 0. Podríamos despejarlo de cualquier otra forma pero esta me ha parecido la más fácil.

b = 128*c - low*c - 128

Sustituimos la b en la segunda ecuación:

c = 255/(high-low)

Bueno, ¿de donde sacamos estos valores low y high? Es tan sencillo como recorrer la imagen comparando los valores de sus pixels para  encontrar el mínimo y el máximo.

Con las imágenes en escala de grises es sencillo pues solo hay un valor maximo y uno minimo. Pero con las de color hay que tomar una decisión. ¿Ajustamos los tres canales de color de forma independiente calculando los valores b y c para cada uno o usamos los mismos para todos los canales?.

Si ajustamos todos los canales con los mismos valores de b y c hay dos opciones:

  • Convertir cada pixel a escala de grises, calcular el mínimo y el máximo y luego los correspondientes b y c
  • Calcular el máximo y mínimo de cada canal y calcular el b y c de aquel que tenga mayor distancia entre el valor máximo y mínimo (high -low). Asi nos aseguramos que coger el canal que “menos hay que estirar”.

¿Cual es el problema ajustar cada color por separado? Que al estirar cada canal por separado la relación entre los colores cambia cambiando el color y no sólo el contraste y la intensidad. Por ejemplo si el rango de cada canal es:

Rojo: 0-255

Verde: 10-230

Azul: 100-150

Tras aplicar nuestro ajuste todos los canales tendrán un rango de 0-255. Así que mientras que el canal rojo no ha cambiado el azul ha variado de forma desproporcionada de tal forma que un píxel RGB con valores (230, 230, 100) pasa a ser tras el ajuste (230, 255, 0). Mientras el canal R y G casi no varía el B pasa de 100 a 0. Estos cambios alteran el color. Puede dar resultados muy artísticos pero poco naturales.

Todos estos cálculos se pueden realizar usando tablas de consulta calculando el valor para cada uno de los posibles valores de cada pixel (0…255)

Evitar el ruido

Vamos a la siguiente vuelta de tuerca. ¿Qué pasa si el valor mínimo y el máximo sólo corresponden a unos pocos píxeles no representativos de la imagen? Al aplicar el ajuste de brillo y contraste podríamos hacer más mal que bien y dejar la imagen peor. ¿Pero ese caso se puede dar? Resulta que “píxeles con valores extremos no representativos de la imagen” describe muy bien cierto tipo de ruidos como el ruido de sal y pimienta. Como siempre la realidad resulta ser menos bonita que la bella teoría. Por suerte hay solución. En lugar de coger los valores mínimos y máximos vamos a descartar una porcentaje pequeño de los valores más altos y más bajos. Por ejemplos vamos a descartar el 5% de los pixeles con los valores más bajos y el 5% con los valores más altos. En caso de que la iamgen esté muy deteriorada por el ruido estos valores pueden ser mayores

Así que hemos de sumar cuantos pixeles de cada valor hay en la imagen. Un momento, eso me suena, huy si, es el histograma y ya lo tenemos hecho. Sólo hemos de ir sumando el número de píxeles de cada entrada del array del histograma hasta que el acumulado supere los valores deseados y usar esos valores como mínimo y máximo. Parece facil y lo es hasta que vemos el histograma a color, tenemos tres canales de color ¿Como elegimos los valores high y low para calcular los ajustes de brillo y contraste?. En el caso anterior hemos ignorado de forma poco elegante este punto y el resultado a veces se resiente. La solución más simple es modificar la función de ajuste de brillo y contraste para que permita modificar cada canal por separado.

Combinar cálculos de tablas de consulta e histogramas.

Ya hemos visto lo útiles que resultan las tablas de consulta para agilizar cálculos y también la utilidad del histograma que al ser una representación estadística de la imagen nos permite calcular ajustes globales de la imagen con solo 256 valores. ¿A que seria genial que ambos métodos se pudieran usar conjuntamente? La vida es bella, ya que no solo es posible sino que es realmente sencillo.

En los distintos métodos que usan el histograma (ajuste del brillo y contraste, ecualización del histograma, …) sencillo que calcular la tabla de consulta correspondiente. Pero al aplicar estas tablas la imagen cambia y por tanto el histograma también. ¿Podemos calcular el nuevo histograma sin tener que volver a recorrer toda la imagen contando pixels? Por fortuna para el interés de esta entrada la respuesta es sí. Además de una forma realmente sencilla.

  1. Creamos un nuevo histograma (newHistogram en el ejemplo) con todos sus valores a 0.
  2. Recorremos todos los posibles valores i del histograma original (por ejemplo de 0 a 255).
  3. Para cada valor i del histograma original consultamos su valor (lut[i]) en la tabla de consulta lut
  4. Se suma el valor del histogram[i] a newHistogram[lut[i]]

for(var i = 0; i < 256; ++i){
  newHistogram[lut[i]] += histogram[i];
}

Con solo recorrer 256 elementos tenemos el histograma actualizado y nos hemos ahorrado volver a recorrer la imagen.

Fenómeno de sincronización con autómatas celulares

En la naturaleza se dan lo que se conoce como fenómenos de sincronización, elementos que se sincronizan solos a partir de un pequeño intercambio de información local.

Si colocas varios metrónomos, cada uno marcando un tiempo, en una superficie que permita la transferencia de movimiento entre ellos acaban sincronizándose.

Las luciérnagas también sincronizan sus destellos hasta terminar destellando al unisono, este proceso tiene una complicación más ya que cada luciérnaga solo percibe a sus vecinas más cercanas por lo que el procesos de intercambio de información ocurre a escala local, pero acaba afectando de forma global.

Como vemos lo que tiene en común es el intercambio de información.

Vamos a simular esto mismo de una forma simple. Para ellos tendremos un autómata celular donde cada celda empieza en un estado de N posibles (4 en nuestro caso: rojo, azul, lima y amarillo) elegido al azar, cada uno de ellos con igual probabilidad. No cuesta mucho imaginar que en un autómata celular con C celdas habrá aproximadamente C/N celdas en cada estado distribuidas uniformemente.

En cada iteración cada celda mirará a sus 8 vecinos, elegirá uno al azar y copiará su estado.

El ejemplo está aquí y el código aquí.

El autómata durante su ejecución

En podríamos pensar que si dejamos funcionar este sistema durante mucho tiempo lo único que ocurrirá es que los individuos irán intercambiando sus estados en un confuso baile de colores. Sin embargo si se deja el tiempo suficiente los estados empiezan a agruparse y a crecer como si compitieran entre ellos hasta que uno a uno comienzan a desaparecer hasta que al final solo queda uno. Todas la celdas han sincronizado su color.

Resultado final con todas las celdas sincronizadas

Si el autómata es pequeño el resultado se alcanza antes, cuando es muy grande tarda mas tiempo en llegar a el, pero al final el resultado es el mismo todas se sincronizan (aunque el color final sea diferente cada vez)

¿Como es esto posible? En teoría no debería cambiar demasiado el estado global, cambiamos aleatoriamente el valor de cada celda copiando el de un vecino que a su vez es aleatorio. Fijaos que ni siquiera es un intercambio inteligente ni muy organizado. Lo que ocurre es que en el azar hay pequeñas desviaciones, pequeñas zonas locales donde un color se vuelve mayoritario esto es posible por el intercambio de información entre vecinos. Cuantos más individuos de ese color existan más probable es que ese estado sea copiado por otros. Cuando un individuo queda rodeado de vecinos en su mismo estado se ha creado un núcleo estable que será difícil de cambiar y que ira creciendo. Solo en la frontera de ese núcleo puede darse el cambio. Basta un pequeño desequilibrio para que el intercambio de información entre vecinos lo realimente y crezca. Al ser un proceso aleatorio hay vaivenes, un color que es mayoritario puede descender bruscamente y otro ocupa su lugar hasta llegado a un punto en que un color es tan mayoritario que acaba imponiéndose.

Debajo puede verse una gráfica con lo % de cada color en la ejecución del autómata.

La evolución en % de los cuatro colores hasta que uno se impone (el 2º)

Caminata aleatoria (random walk) en un autómata celular

Una caminata aleatoria, random walk en inglés, es un proceso aleatorio donde una partícula (aquí partícula es una forma elegante de decir “cosa”) se mueve avanzando en direcciones aleatorias donde la elección en cada momento no está condicionada por ninguna elección anterior. Se suele ilustrar con el paseo realizado por un caminante borracho que elige aleatoriamente entre alguna de las posibles direcciones que tiene alrededor y da un paso en esa dirección. Tras ese paso repite el proceso. En nuestra simulación los pasos van a tener todos el mismo tamaño, una celda. Para evitar las diagonales (que seria avanzar 1,41 veces mas que por lo lados) se va a usar una vecindad de tipo von Neumann lo que deja solo cuatro vecinos por celda y solo cuatro direcciones en las que el caminante se puede mover (arriba, abajo, izquierda, derecha)

Los estados de las celdas son solo dos, si contiene al caminante o no. Para mejorar la visualización se suele definir un tercer estado que indica si la celda ha sido visitada alguna vez o no por el caminante. Este tercer estado es algo simplemente visual y no afecta en nada a los movimientos del caminante.

  • Negra, celda sin visitas
  • Blanca, celda visitada
  • Azul, indica la celda donde esta el caminante

Las reglas son muy simple, la celda donde este el caminante elige una de sus cuatro vecinas y se mueve ahí.

El simulador se puede ver aquí y el código aquí.

El autómata celular Ulam-Warburton

El autómata celular Ulam-Warburton (UWCA) genera un patrón fractal bidimensional usando autómatas celulares con vecindad de von Neumann con dos estados: encendido y apagado. Comienzan con una celda encendida(generalmente el central) y los demás apagadas. La regla que genera el autómata es muy sencilla, si tienes un único vecino encendido cambias tu estado a encendido. Cuando una celda se enciende ya no se apaga.
Por motivos de visualización se puede añadir un nuevo estado que sería “recién encendido” ese estado lo tienen las céldas que acaban de cambiar de estado y solo dura un ciclo antes de cambiar a encendido.

En el ejemplo cada estado viene indicado con un color:

  • negro: apagado
  • blanco: encendido
  • rojo: recién encendido

Se puede probar en este simulador https://cubiwan.github.io/cellularAutomataExamples/UlamWarburton.html y el código puede verse aquí.

Veamos algo de teoría, a partir del segundo ciclo el número de celdas nuevas en cada ciclo sigue esta formula:

Donde wt corresponde a :

¿Cómo calculamos el sumatorio hasta infinito? Vamos a hacerlo de una forma matemáticamente poco elegante y burda pero fácil de entender. Como 2^k crece muy rápido vamos a calcular el sumatorio solo de los primeros 30 valores (y con menos también serviría).

Debajo dejo el código JS para calcular el valor de wt y de u:

function sumWt(n, k){
    if(k == 0) {
        return 0;
    } else {
        return sumWt(n, k-1)+Math.floor(n/Math.pow(2,k))
    }
}

function wt(n){
    return n - sumWt(n, 30) //calcular hasta k = 30;
}

function u(n){
    return 4/3 * Math.pow(3, wt(n-1));
}

Vamos a calcular unos cuantos resultados para wt

0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6

Podemos intuir una especie de patrón, para verlo claro vamos a quitar las comas y cada vez que veamos un 1 empezaremos una nueva línea:

1
12  
1223  
12232334
1223233423343445
12232334233434452334344534454556

Vemos que el patrón es sencillo, tomamos todos los resultados donde el último 1 y los repetimos dos veces, al segundo grupo le sumamos uno.

1 => 1
1 (1+1) => 12
12 (1+1)(2+1) => 1223
1223 (1+1)(2+1)(2+1)(3+1) => 12232334

Si wt tiene ciclos significa que el número de celdas nuevas (u()) también lo tiene.da igual lo que haya crecido al estructura de celdas activas, habrá un punto en que solo haya 4 y siempre se repetirán los mismo números: 4,4,12,4,12,12,36,4,12,….

En la gráfica de número de celdas nuevas se puede ver esta repetición de subidas y caídas:

Este numero de repeticiones esta ligado con su estructura fractal.

El autómata celular Ulam-Warburton original usa la vecindad de von Neumann pero presenta un comportamiento similar usando la vecindad de Moore, si bien cambia el patrón:

Regresión lineal con incertidumbre en Arduino

Vamos a empezar este texto develando el truco que usaremos para representar incertidumbre con la regresión lineal para Arduino y que se basa en emplear la versión con pesos del algoritmo de regresión lineal. La incertidumbre estará representada como valores con una variación de pesos según la certeza que tengamos de su valor. Usaremos el peso como porcentaje de certeza de ese dato

Esta no es la mejor ni la única manera de hacerlo. No hay que olvidar que aquí se trata de hacerlo en algo tan limitado en memoria y potencia como pude ser un Arduino UNO.

Usaremos la librería regressino, en concreto su librería para regresión lineal:

#include <LinearRegression.h>

LinearRegression lr = LinearRegression();

Una forma de incertidumbre es cuando directamente tenemos valores de los que “nos fiamos” menos que de otros. Por ejemplo, porque vienen de dos fuentes distintas. En este caso los datos menos fiables tendrán que tener un peso más bajo que los más fiables para que su influencia sobre el resultado final sea menor.

//datos fuente no fiable
lr.learn(1, 3, 0.5);
lr.learn(2, 5, 0.5);
lr.learn(3, 6, 0.5);

//datos fuente fiable
lr.learn(2, 4, 1);
lr.learn(4, 5, 1);
lr.learn(5, 6, 1);

Pero hay otro caso de incertidumbre, cuando no conocemos el valor del dato con seguridad, lo que conocemos son los valores entre los que está comprendido. Generalmente tenemos dos valores, un mínimo y un máximo o tres valores con uno más probable y un mínimo y un máximo (a veces representados como errores) entre los que ese valor puede variar. En este caso tenemos que definir cómo se distribuye el peso (probabilidad) entre estos valores. Hay que recordar que la suma total de los pesos tiene que ser igual a 1.

Una vez definida la forma en que se distribuye la probabilidad hay que descuartizarla en puntos. La idea es que esto funcione en un Arduino UNO y no podemos trabajar directamente con funciones de probabilidad.

Supongamos que para x = 10 sabemos que el valor de y está comprendido entre 2 y 3. Ahora hay que saber cómo está distribuida la probabilidad entre esos dos valores. Veamos algunas posibilidades:

  • Toda la probabilidad se concentra en cada uno de esos valores por lo tanto el 2 tendría un 50% de certeza y el 3 otro 50%. O lo que es lo mismo un peso de 0.5
lr.learn(10, 2, 0.5);
lr.learn(10, 3, 0.5);
  • La probabilidad se distribuye de forma uniforme por todo el espacio entre esos dos puntos. Para representarlo tómanos varios puntos entre 2 y 3 y les asignamos a todos la misma probabilidad.
lr.learn(10, 2, 0.2);
lr.learn(10, 2.25, 0.2);
lr.learn(10, 2.5, 0.2);
lr.learn(10, 2.75, 0.2);
lr.learn(10, 3, 0.2);
  • El punto central es mucho más probable que los extremos. Un ejemplo de como podemos hacerlo.
lr.learn(10, 2, 0.25);
lr.learn(10, 2.5, 0.5);
lr.learn(10, 3, 0.25);
  • Si por ejemplo queremos simular una distribución en campana de media u y con desviación estándar s
lr.learn(10, u, 0.682);
lr.learn(10, u-s, 0.136);
lr.learn(10, u-(2*s), 0.023);
lr.learn(10, u+s, 0.136);
lr.learn(10, u+(2*s), 0.023);

Regresión lineal con pesos en Arduino

Ya hemos visto varias formas de extender las capacidades de la regresión lineal en Arduino. En este caso vamos a asociar pesos a los valores para que no todos los casos aporten los mismo al resultado final. Pero qué significa “aportar” más al resultado, de forma gráfica podríamos imaginarnos que los puntos de mayor peso atraen más a la recta de la regresión lineal por lo que esta tiende a acercarse más a estos. La utilidad de este sistema es cuando tenemos resultados que por algún motivo valoramos más que otros.

Vamos a modelar el peso como un valor entre 0 y 1. De tal forma que el peso máximo corresponda con 1 y el mínimo con 0. A mayor peso más “aporta” ese valor al resultado. Entre dos valores uno con peso 1 y otro con peso 0.5 el primer cuenta el doble que el segundo. Cuando el peso es 1 no hay diferencia con la regresión lineal sin pesos. Y cuando el peso es cero el valor no va a aportar nada al resultado.

Partimos de la función de regresión lineal que ya vimos en otro post:

void LinearRegression::learn(double x, double y){
    n++;
    meanX = meanX + ((x-meanX)/n);
    meanX2 = meanX2 + (((x*x)-meanX2)/n);
    varX = meanX2 - (meanX*meanX);

    meanY = meanY + ((y-meanY)/n);
    meanY2 = meanY2 + (((y*y)-meanY2)/n);
    varY = meanY2 - (meanY*meanY);

    meanXY = meanXY + (((x*y)-meanXY)/n);

    covarXY = meanXY - (meanX*meanY);

    m = covarXY / varX;
    b = meanY-(m*meanX);
}

Como ya hemos visto en otro post vamos a usar “el truco” que consiste en transformar el valor de x e y que se le pasa a la función que calcula la regresión lineal (learn). Podríamos verlo como que el valor de x se usan para modificar el valor de meanX y meanX2 y el de y para meanY y meanY2. Entonces si w es el peso:

  • Si el peso es 1 x e y no cambian su valor
  • Si es 0 x e y no tienen que afectar a los valores de meanX y meanY para que pase eso el valor de x ha de ser igual que meanX y el de y igual a meanY.
  • Si el peso este entre 0 y 1 el valor ha de componerse con el valor de x e y y el valor de las medias de cada uno.

Para conseguir eso vamos a ponderar el valor de la x y el de meanX según w

x = x*w + meanX*(1-w);
y = y*w + meanY*(1-w);

Solo queda añadir un par de comprobaciones para evitar que el valor pueda ser mayor de 1 o menor de 0:

void LinearRegression::learn(double x, double y, double w){
    if(w >= 1) { 
        learn(double x, double y);
    } else if(w <= 0) {
        return;
    } 

    x = x*w + meanX*(1-w);
    y = y*w + meanY*(1-w);
    learn(double x, double y);
}

Todo esto se puede ver en la librería Regressino.

Por último comentar que hay dos posible optimizaciones que se podrían aplicar si fuera necesario y que nos permiten ahorrar algunos cálculos si hay muchos pesos cercanos a 0 o a 1:

  • Los pesos muy cercanos a 0 se ignoran
  • Los pesos muy cercanos a 1 se tratan como si no tuvieran peso


Hay una pequeña pérdida de precisión pero en el caso de tener muchos datos puede compensar al reducir el tiempo de cálculo.

Excepciones y control de errores en Arduino

Uno de los puntos débiles de las placas más básicas de Arduino es la gestión de errores, siguen la política de “nunca me equivoco” de tal forma que acciones o código que en otros sistemas lanzarían error no producen ninguno en Arduino.

Un excepción es un mecanismo en el software que “salta” cuando se produce un error y permite que el software ejecute una rutina de gestión de errores que se ocupa de gestionarlo para después recuperar su funcionamiento normal, si es posible. Arduino no permite usar el mecanismo habitual de C++, las instrucciones try y catch. Es posible usarlas en el código sin que den error al compilar pero durante el proceso de compilación se desactiva su funcionamiento. No es algo que se haga de forma arbitraria, simplemente no funcionan en Arduino. Si embargo vamos a tratar de conseguir un mecanismo similar que nos permite una gestión de errores muy básica . Para ello emplearemos dos funciones (setjmp y longjmp) y un tipo de variable (jmp_buf). esta funciones permiten establecer un punto de salto dentro de una función al que se puede ir desde cualquier parte del programa:

  • setjmp – establece el punto donde se va recuperar la ejecución del programa hay que pasarle como parámetro una variable de tipo jmp_buf.
  • jmp_buf – es un tipo de variable que almacena la información necesaria para restaurar la ejecución del código en el punto indicado por setjmp.
  • longjmp – salta al punto establecido por setjmp. Requiere dos parámetros uno de tipo jmp_buf que indica a que setjmp va a saltar y otro que indica el código de error que va devolver setjmp tras el salto. Este código nunca puede ser 0 si se usa 0 devolverá 1.

Ahora veamos como se usa, para entenderlo bien el código hay que saber los siguientes detalles:

  • Es necesario incluir la librería setjmp.h
  • setjmp la primera vez que se llama se usa para fijar el punto de salto y devuelve el valor cero
  • Cuando se llama a longjmp es como si el programa continuara desde donde se llamo a setjmp pero en lugar de devolver 0 devuelve otro valor
  • Para que esto funcione correctamente setjmp tiene que estar dentro de un if o un switch.
  • Para que todo funcione correctamente setjmp ha de fijar el punto de salto dentro del loop, si lleva a otras funciones puede fallar al llegar al final de la función donde ha saltado

En el caso más simple solo tenemos un tipo de errores y por tanto cada vez que se produce un error la forma de gestionarlo es siempre la misma. Para ello basta con usar un if.

#include <setjmp.h>

jmp_buf exception_mng;
int a = 5;
int b = 1;
int c;

void setup() {
  Serial.begin(9600);
}

void loop() { 
  if (setjmp(exception_mng)) { //si se produce error
		Serial.println("EXCEPTION");
  }
  a--;
  divide();
  delay(1000);    
}

void divide(){
  if(a == 0){
    longjmp(exception_mng, 1);
  }
  c = b/a;  
  Serial.print(b);
  Serial.print("/");
  Serial.print(a);
  Serial.print(" = ");
  Serial.println(c);
}

Una versión más avanzada permite gestionar distintos tipos de incidencias según el valor que se le pase a longjmp como segundo parámetro

#include <setjmp.h>

jmp_buf exception_mng;
int a = 5;
int b = 1;
int c;

void setup() {
  Serial.begin(9600);
}

void loop() { 
  switch (setjmp(exception_mng)) {
	case 0: //sin errores
		break;
  case 1: //division por cero
		Serial.println("EXCEPTION DIVISION BY 0");
    break;
  case 2: //divisor negativo
		Serial.println("EXCEPTION DIVISION BY NEGATIVE NUMBER");
    break;
  default: //se ejecuta cuando no se cumple ninguno de los casos anteriores
		Serial.println("GENERIC EXCEPTION");
    break;

  }
  a--;
  divide();
  delay(1000);    
}

void divide(){
  if(a == 0){
    longjmp(exception_mng, 1);
  }
  if(a < 0){
    longjmp(exception_mng, 2);
  }
  c = b/a;  
  Serial.print(b);
  Serial.print("/");
  Serial.print(a);
  Serial.print(" = ");
  Serial.println(c);
}

Este sistema nos dota de un mecanismo básico de gestión de errores aunque nos obliga a nosotros a realizar la comprobación para lanzar la “excepción”.

Generador de código a partir de plantillas

Vamos a ver cómo funciona chicote un generador de código basado en plantillas. Hace tiempo tuve que hacer un trabajo muy repetitivo de migración de código de una tecnología a otra, al final el 90% del trabajo era repetitivo. No tan simple como copiar y pegar, pero nada que requiriera demasiado cerebro. Para esos casos cree chicote. Chicote es el nombre de un famoso chef español. Por eso el uso de términos relativos a la cocina.

Vamos a ver de que consta este sistema.

Motor de plantillas

Lo primero que necesita un generador basado en plantillas es un motor de plantillas que genere texto a partir de una plantilla.

Para este caso opte por mustache.js que es simple, ligero y muy fácil de aprender. Además permite personalizar varios aspectos que necesitaba.

Una de sus principales limitaciones es que no se le puede pasar “parámetros” a las etiquetas, para resolverlo tuve que modificar el código añadiendo esa posibilidad.

Operadores de texto

Otra cosa necesaria para generar código amigable para los seres humanos es poder operar sobre el texto. Es habitual que el mismo nombre se escriba en capital, mayúsculas, minúsculas, separado por guiones,… Los programadores somos unos maniáticos de esto y definimos documentos de estilo solo para especificar este tipo de cosas.

Para esto elegí dos librerías voca.js y pluralize.js y transforme sus operaciones en etiquetas que se puedan usar desde la plantilla.

Además son útiles funciones que actúen a nivel de línea para ordenarlas, limpiar espacios, eliminar líneas duplicadas o tabularlas.

Generador de datos falsos

Los datos falsos con cierta estructura son útiles para generar test, plantillas o demos. En este caso recurrí a la librería faker.js

Algunas ayudas más

Finalmente añadí alguna ayuda más con función habituales en programación como la fecha, la hora, un timestamp, números aleatorios o un contador

Directorios y ficheros

Otra necesidad de los generadores de código programador es poder generar directorios y nombres de archivos con algún tipo de plantilla. Es muy habitual usar la estructura de directorios para organizar el código.

El cocinero (Chicote)

Chicote es un generador agnóstico de código basado en plantillas. Es agnóstico puesto que sirve para cualquier lenguaje que use ficheros de texto. Necesita NodeJS para funcionar. No necesitas instalar nada, sólo descargar el código y ejecutar cooking.js

node cooking.js

La salida se genera en el directorio output

La receta (recipes.json)

Necesitas crear un archivo recipes.json, que contiene una o mas recetas, cada receta contiene todos los ingredientes y pasos para generar código. El único apartado obligatorio en cada receta es steps ,un array con donde se indican que pasos (steps) tiene que ejecutar para realizar la receta. Un step es el nombre de un archivo JSON en el directorio steps.

Ejemplo de fichero recipes.json con solo una receta:

[
{	
    "name": "Test",
	"author": "Bob",
	"fields": [
		{"name": "id", "type": "int"},
		{"name": "firstname", "type": "String"},
		{"name": "lastname", "type": "String"},
		{"name": "birthday", "type": "date"}
	],
	"names": ["HelloWorld", "helloWorld", "hello-world"],
	"steps": ["example"]
}
]

Pasos de la receta (step)

Un step es un archivo JSON en el directorio “steps” donde se preparan los ingredientes. Un paso tiene tres partes: variables (vars), directorios (directories), plantillas (templates)

  • vars son variables creadas a partir de los ingredientes. Actúan de forma similar a los ingredientes.
  • directories indica los directorios de salida.
  • plantillas archivos de texto en el directorio de plantillas. Se compone de un array de dos cadenas de texto, la primera indica la ruta de la plantilla en el directorio templates, la segunda la ruta del fichero generado en el directorio output

Se pueden usar los tags que más adelante veremos para calcular lo valores de estos apartados.

Ejemplo de step:

{
    "vars":[
        ["filename1", "exampleA-{{timestamp}}.code"],
        ["filename2", "exampleB-{{timestamp}}.code"],
        ["className", "{{#capital}}{{name}}{{/capital}}"],
        ["varName", "{{#decapital}}{{name}}{{/decapital}}"],
        ["moduleName", "{{#dash}}{{name}}Module{{/dash}}"]
    ],	
    "directories":[
        "example/example1",
        "example/example2",
        "examples/example1/example2"
    ],
    "templates":[
        ["example/example1.text", "text.txt"],
        ["example/example1.text", "example/example1/{{filename1}}"],
        ["example/example2.text", "example/example2/{{filename2}}"],
        ["example/example1.text", "examples/example1/{{filename1}}"],
        ["example/example2.text", "examples/example1/example2/{{filename2}}"]
    ]
}

El cocinado (templates)

Las plantillas son archivos de texto en el directorio “templates” que utilizan la sintaxis de mustache pero con algunas “etiquetas especiales”.

{{timestamp}}  
{{date}} - fecha en formato yyyy-mm-dd
{{time}} - hora en formato hh:mm:ss
{{year}} - año
{{month}} - mes
{{day}} - dia
{{hour}} - hora
{{minute}} - minuto
{{second}} - segundo
{{GUID}}   

{{#plural}}text{{/plural}}  
{{#camel}}text{{/camel}}  
{{#capital}}text{{/capital}}   
{{#decapital}}text{{/decapital}}  
{{#dash}}text{{/dash}}  
{{#snake}}text{{/snake}}  
{{#swap}}text{{/swap}}  
{{#title}}text{{/title}}  
{{#lower}}text{{/lower}}  
{{#upper}}text{{/upper}}  
{{#slug}}text{{/slug}}  
{{#reverse}}text{{/reverse}}  
{{#stripTags}}text{{/stripTags}}  
{{#escHtml}}text{{/escHtml}}  
{{#unHtml}}text{{/unHtml}}  
{{escRegExp}}text{{/escRegExp}}  
{{#trim}}text{{/trim}}  
{{#latin}}text{{/latin}}  

{{#count}}text{{/count}} - reemplaza texto por número de caracteres    
{{#countWords}}text{{/countWords}} - reemplaza el texto por el número de palabras  

{{#delSpaces}}text{{/delSpaces}} - suprimir todos los espacios  
{{#delDuplicateSpaces}}text{{/delDuplicateSpaces}}  - eliminar los espacios duplicados   
{{#delLast|C}}text{{/delLast|C}} - suprimir la última coincidencia de caracteres C  
{{#delFirst|C}}text{{/delFirst|C}} - suprimir la primera coincidencia de caracteres C  
{{#delEnd|N}}text{{/delEnd|N}} - suprimir N caracteres del final  
{{#delStart|N}}text{{/delStart|N}} - del N caracteres desde el inicio  
  
{{#repeat|N}}text{{/repeat2|N}} - repetir el texto N veces  
  
{{#sortAscL}}text{{/sortAscL}} - ordenar las líneas de forma ascendente   
{{#sortDescL}}text{{/sortDescL}} - líneas de ordenación descendente  
{{#naturalSortAscL}}text{{/naturalSortAscL}} - líneas de ordenación natural ascendente  
{{#naturalSortDescL}}text{{/naturalSortAscL}} - líneas de ordenación natural descendente  
{{#shuffleL}}text{{/shuffleL}} - barajar líneas  
{{#trimL}}text{{/trimL}} - recortar líneas  
{{#joinL}}text{{/joinL}} - unir líneas  
{{#removeDuplicateL}}text{{/removeDuplicateL}} - eliminar líneas duplicadas  
{{#spaceL|N}}text{{/spaceL|N}} - Añadir N espacios al principio de cada línea   
{{#tabL|N}}text{{/tabL|N}} - Añadir N tabulaciones al principio de cada línea  
{{#addStartL|C}}text{{/addStartL|C}} - Añadir el carácter C al principio de cada línea   
{{#addEndL|C}}text{{/addEndL|C}} - Añadir el carácter C al final de cada línea  

{{#log}}text{{/log}} - escribir texto en la consola   
{{#eval}}text{{/eval}} - evalúa el texto como código JS  
{{#R}}texto{{/R}} - renderizar texto  
  
{{#C=|N}}{{/C=|N}} - poner el contador en N  
{{#C+|N}}{{/C+|N}} - aumentar el contador en N  
{{#C-|N}}{{/C-|N}} - reducir el contador en N  
{{#C}}{/C}} - imprimir el contador  

{{#K}}texto{{/K}}  - cargar datos de la base de conocimiento (cookbook.json)  
  
{{!text}} - Comentario  

{{#faker}}data{{\faker}} - genera un dato aleatorio usando faker.js

{{=AA BB=}} - Cambia los caracteres para indicar que es un tag de {{ }} to AA BB

Ejemplo de uso de alguno de los tags:

helloWorld
plural: helloWorlds
camel: helloWorld
capital: HelloWorld
decapital: helloWorld
dash: hello-world
snake: hello_world
swap: HELLOwORLD
title: HelloWorld
lower: helloworld
upper: HELLOWORLD
escHtml: helloWorld
slug: hello-world
count: 10
countWords: 2

hello-world
plural: hello-worlds
camel: helloWorld
capital: Hello-world
decapital: hello-world
dash: hello-world
snake: hello_world
swap: HELLO-WORLD
title: Hello-World
lower: hello-world
upper: HELLO-WORLD
escHtml: hello-world
slug: hello-world
count: 11
countWords: 2

El código de Chicote incluye un ejemplo de plantillas.

El libro de cocina (cookbook.json)

Actúa como base de conocimiento para Chicote. Funciona como un sistema de clave valor.

Para leer los datos de la base de conocimiento se usan los tags {{#K}} y {{/K}}. Si tenemos {{#K}}texto{{/K}} texto será reemplazado por el valor que se encuentre en la base de conocimiento con la clave texto

Un ejemplo de base de conocimiento

{	
	"intDefaultValue": " = 0",
	"dateDefaultValue": " = new Date()",
	"stringDefaultValue": " = ''"
}

Ahora veamos un ejemplo de su uso en una plantilla:

var v{{#K}}{{#lower}}{{type}}{{/lower}}DefaultValue{{/K}};

En el ejemplo si type es date dará como resultado:

var v{{#K}}dateDefaultValue{{/K}};

Buscando esa clave en la base de conocimientos:

var v = new Date();

La foto del plato

Como pequeño resumen se puede ver este diagrama que trata de simplificar el funcionamiento de Chicote:

chicote

Bon appetit