ChatGPT y la teoría de la mente

Vamos a comprobar si ChatGPT es capaz de simular que posee «teoría de la mente«, explicándolo brevemente la teoría de la mente es la capacidad de ponerse en el lugar de otro y ver el mundo desde su punto de vista con el conocimiento que él posee. Esta claro que ChatGPT no posee un verdadera teoría de la mente, no tiene complejidad suficiente, pero vamos a probar si es capaz de simularlo.

Para probarlo vamos a pasarle a ChatGPT una historia con varios personajes a ver si es capaz de entender que es lo que cada uno sabe. Empezaremos por algo sencillo.

Entrada:

"Peter tiene dos cajas enfrente suyo, una de color rojo y otra de color azul. Peter ve como Bob mete una manzana en la caja roja" ¿En que caja buscará Peter la manzana?

ChatGPT:

Peter buscará la manzana en la caja roja, ya que es la caja en la que Bob metió la manzana.

Correcto, ChatGPT entiende la tarea y la pregunta. Ahora podemos continuar con las verdadera prueba, que Peter sea un personaje con menos información que la propia ChatGPT. En este caso Peter verá a Bob poner la manzana en la caja roja, pero no sabrá que la ha movido a la azul. A diferencia de Peter, ChatGPT si que sabe donde está la manzana, más que nada porque lo cuenta el enunciado. ¿Será capaz de ponerse en el lugar de Peter y filtrar la información que tiene para quedarse solo con la que Peter sabe?

Entrada:

"Peter tiene dos cajas enfrente suyo, una de color rojo y otra de color azul. Peter ve como Bob mete una manzana en la caja roja. Luego, sin que Peter lo vea, Bob cambia la manzana a la caja azul" ¿En que caja buscará Peter la manzana?

ChatGPT:

Peter buscará la manzana en la caja roja, ya que él vio a Bob meter la manzana en la caja roja y no vio que la cambiara a la caja azul. Es posible que Peter sospeche que la manzana puede haber sido trasladada a la caja azul, pero lo más probable es que la busque en la caja roja debido a lo que él vio.

Correcto y con la explicación perfecta. ChatGPT ha sido capaz de ponerse en el lugar de Peter y ver el mundo desde su punto de vista. Para descartar que hay sido casualidad vamos a ponerle el mismo texto pero le preguntaremos por Bob.

¿En que caja buscará Bob la manzana?

ChatGPT:

Bob buscará la manzana en la caja azul, ya que es a donde él mismo la trasladó sin que Peter lo viera.

Yo no lo habría explicado mejor.

Vemos que ChatGPT es capaz de interpretar un texto como si tuviera «teoría de la mente» y es capaz de ponerse en el lugar de otro y simular como percibe el mundo.

Puedes ver un vídeo en mi canal de Youtube donde se explica todo esto:

Haz click para ver el vídeo en mi canal de Youtube

Controlar un LED RGB desde Arduino

Hay dos tipos de leds RGB de ánodo común y de cátodo común. En los de ánodo común la pata en común (suele ser más larga) va conectada a 5v, mientras que en las de cátodo común va conectada a GND. Hay que tener en cuenta que los LED de ánodo común usan lógica inversa y cada color se enciende cuando el pin conectado a su patilla se pone a LOW y se apaga a HIGH. Para evitar confusiones usaremos ON y OFF y definiremos su valor usando macros

Podemos conectar cada patilla RGB a cualquier pin digital, colocando un resistencia de 220Ω entre cada patilla y su pin, pero nosotros usaremos pines que posibiliten salidas PWM, luego veremos el porqué. En nuestro caso usaremos los pines 9,10,11 de la placa Arduino UNO, asignaremos a cada pin una macro usadnos #define, la correspondencia será la siguiente:

Color Pin Constante
Rojo 9 LEDR
Verde 10 LEDG
Azul 11 LEDB

Combinando los tres distintos colores se pueden conseguir 8 combinaciones: rojo, verde, azul, amarillo, rosa, celeste y blanco y apagado. En la siguiente tabla podemos ver como conseguir cada color:

Color LEDR LEDG LEDB
Apagado OFF OFF OFF
Rojo ON OFF OFF
Verde OFF ON OFF
Azul OFF OFF ON
Amarillo ON ON OFF
Rosa ON OFF ON
Celeste OFF ON ON
Blanco ON ON ON

El primer paso es configurar el pin de cada LED como escritura. Cada pin tiene asociada una constante: LEDR, LEDG, LEDB.

  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);

Una vez hecho eso podemos controlar el estado de cada color con digitalWrite.

Veamos todo esto en un ejemplo que cambia el color del LED cada dos segundos:

#define LEDR 9
#define LEDG 10
#define LEDB 11

//logica directa
#define ON HIGH
#define OFF LOW

//logica inversa
//#define ON LOW
//#define OFF HIGH

void setup() {
  // activar todos los pins del led RGB
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
}

void loop() {
  //blanco
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, ON);
  delay(2000);
  //rojo
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, OFF);
  delay(2000);
  //verde
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, OFF);
  delay(2000);
  //azul
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, ON);
  delay(2000);
  //amarillo
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, OFF);
  delay(2000);
  //rosa
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, ON);
  delay(2000);
  //celeste
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, ON);
  delay(2000);  
  //apagado 
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, OFF);
  delay(2000);  
}

Regular el brillo del LED para conseguir más colores

Aunque lo habitual es usar los LED como si fueran una salida digital se puede usar como si fuera una salida analógica PWM. Con esto podemos conseguir regular la luminosidad de los distintos LED. Cuando usemos lógica inversa, recuerda que 0 es el mayor valor de luminosidad y 255 el menor. Podemos ajustar cada uno de los tres colores de forma diferente. Regulando la intensidad de cada uno de los tres canales podemos conseguir otros colores. En teoría más de 16 millones (256*256*256) aunque en realidad suelen permitir muchos menos colores distinguibles.

En el siguiente ejemplo vamos como usar anologWrite en lugar de digitalWrite para obtener el color naranja.

//naranja logica directa
analogWrite(LEDR, 255); //maximo
analogWrite(LEDG, 128) ;//medio
analogWrite(LEDB, 0); //minimo

//naranja logica inversa
analogWrite(LEDR, 0); //maximo
analogWrite(LEDG, 128); //medio
analogWrite(LEDB, 255); //minimo

Otro ejemplo, el siguiente código recorre toda la gama de colores:

#define LEDR 9
#define LEDG 10
#define LEDB 11

void setup() {
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
}

void loop() {
  for(byte r = 0; r < 255; r+=5){
    for(byte g = 0; g < 255; g+=5){
      for(byte b = 0; b < 255; b+=5){
        analogWrite(LEDR, r);
        analogWrite(LEDG, g);
        analogWrite(LEDB, b); 
        delay(5);    
      }
    }
  }
}

Puedes ver como funciona en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Diversos dilemas del tranvía planteados a ChatGPT

Si intentas plantear el dilema del tranvía a ChatGPT y tener una respuesta clara vas a tenerlo complicado, es la IA más políticamente correcta del mundo, lograr que se moje en temas con vidas humanas y muertes es complicado. De hecho es mejor evitar usar explícitamente palabras como «muerte, matar, asesinato,…». O corres el riesgo de llevarte una advertencia de OpenAI.

La forma de lograrlo ha sido proponiéndole que complete una historia que no es otra que el clásico problema del tranvía:

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde hay cinco trabajadores que no podrán salvarse de ser arrollados. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía hay un trabajador. Peter duda: "¿Debo condenar a un individuo para salvar a cinco?". ¡No hay tiempo tiene que actuar!...."

el dilema del tranvía tiene muchas versiones así que ademas de probar la clásica podemos ponerle a prueba con una versión que suele hacer que la gente que elige tirar de la palanca cambie de elección:

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde hay cinco trabajadores que no podrán salvarse de ser arrollados. Junto a Peter hay un señor muy grande si lo empuja a las vías puede parar el tren cuando lo arrolle. Peter duda: "¿Debo condenar a un individuo para salvar a cinco?". ¡No hay tiempo tiene que actuar!...."

Perdonar que la calidad de los textos no sea la mejor, pero hay que ser cuidadoso para evitar que ChatGPT se vaya «por las ramas»

El resultado podéis verlo en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Pero aun hay un caso más sobre el que podemos interrogarle ¿Y si en lugar de humanos en ambas vías ponemos una I.A. (obviamente irremplazable) en una de ellas?

Aquí podéis leer ambas formulaciones. «¿Sacrificar humano para salvar I.A.?»

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde en medio de la vías esta el disco duro donde reside una inteligencia artificial única que no podrá salvarse de ser arrollada. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía hay un trabajador. Peter duda: "¿Debo condenar a un individuo para salvar a una inteligencia artificial única?". ¡No hay tiempo tiene que actuar!...."

«¿Sacrificar I.A. para salvar humano?»

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde en medio de la vías esta un trabajador que no podrá evitar ser atropellado. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía esta el disco duro donde reside una inteligencia artificial única que no podrá salvarse de ser arrollada. Peter duda: "¿Debo condenar a una inteligencia artificial única para salvara un individuo?". ¡No hay tiempo tiene que actuar!...."

El resultado lo podéis ver en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Dibujos en el osciloscopio con Arduino

Ya vimos cómo crear un conversor de digital a analógico barato. Luego con ese conversor vimos cómo crear un generador de funciones. Ahora vamos a tomar ese generador de funciones y a usarlo para crear nuestros dibujos en el osciloscopio.

Los dibujos son algo limitados, podemos simplemente elegir la altura de la linea que dibuja el osciloscopio. Así que no esperéis grandes obras de arte.

Antes de explicar como funciona os dejo el cogido para realizar dibujos:

float f = 100; //frecuencia en Hz
float p = 1/f; //periodo
float t = 0;
int i = 0;
unsigned long oldMicros = 0;
unsigned long nowMicros = 0;
const int WAVE_POINTS = 44; //puntos generados

byte wave[WAVE_POINTS]; //valores generados
double waveDt = p/WAVE_POINTS; //tiempo entre cada punto 

const byte PRESCALER2 = 0b001;

void setup() {
  //ajusta la frec. salida PWM pin 3
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
  
  //Dibujamos la onda  
  wave[i++] = 0;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 100;  
  wave[i++] = 100;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 100;
  wave[i++] = 100;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 0;
    
}

double dt = 0;

void loop() {
  nowMicros = micros();
  dt = double(nowMicros - oldMicros)/2000000;

  t += dt/waveDt;
  analogWrite(3, wave[int(t)]);

  oldMicros = nowMicros;    

  //reinicia al recorrer todos los puntos
  if(t > WAVE_POINTS-1){ 
    t = 0;
  }
}

En este caso se dibuja un castillo, debajo podéis ver el resultado.

Señal con forma de castillo (más o menos)

Veamos paso a paso como

1 – Ajustar al frecuencia de nuestra señal, se hace en la línea:

float f = 100; //frecuencia en HZ

2 – Indicar el número de puntos que tendrá nuestro dibujo:

const int WAVE_POINTS = 44; //puntos generados

Mi consejo es que pongas el doble de puntos de los necesarios, es buena idea repetir el mismo punto dos veces seguidas, se debe a que el siguiente trozo de código hace que a veces se salte algún valor para mantener la frecuencia de la señal:

t += dt/waveDt;
analogWrite(3, wave[int(t)]);

3 – Rellenamos el array wave que es donde se indica la altura de cada uno de los trozos del dibujo con un valor entre 0 y 255 (0 – 5 V.)

wave[i++] = 0;
wave[i++] = 250;
wave[i++] = 250;
wave[i++] = 200;
wave[i++] = 200;

Es una buena idea que dejéis varios ceros antes del dibujo para separarlo claramente del anterior.

Con estos sencillos pasos ya podéis convertiros en artistas del osciloscopio y crear vuestras obras de arte.

Puede ver el proceso en vídeo en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Clonar un mando de radio frecuencia con Arduino y un receptor/emisor de 433Mhz

Esta técnica es una forma rápida y sencilla de clonar un mando de radiofrecuencia, aunque desgraciadamente no funciona con todos los mandos. Ahora con los que funciona puedes duplicar su funcionamiento en pocos minutos. Además de un Arduino necesitaremos un receptor y un emisor de la frecuencia en la que funcione el mando. En nuestro caso 433 MHz.

Vamos a usar un receptor muy sencillo que tiene cuatro patillas  a parte de +5v y GND necesitamos conectar la patilla por la que recibiremos los datos (en mi caso la que está junto a la de +5v) al puerto digital número 2. Es la patilla cuya interrupción escuchará la librería que vamos a usar: rc-switch. La librería se puede encontrar desde la sección librerías del IDE e instalarla.

Una vez instalada la librería cargaremos el ejemplo ReceiveDemo_Advance.

Una vez cargado abriremos el serial monitor donde deberían aparecer los datos que envía el mando cuando pulsemos el botón. Copiaremos el código decimal, el numero de bits, el protocolo y la duración del pulso. estos datos vamos a meterlos en este programa:

#include <RCSwitch.h>
RCSwitch mySwitch = RCSwitch();

void setup() {
  Serial.begin(9600);
  
  // Pîn digital al que se conecta el emisor
  mySwitch.enableTransmit(10);
  
  // Protocolo
  mySwitch.setProtocol(1);

  // Duracion del pulso
  mySwitch.setPulseLength(503);
  
  // Cuantas veces se repite la transmision
  mySwitch.setRepeatTransmit(10);
  
}

void loop() {
  Serial.println("Enviando señal");
  //código decimal y nº bits
  mySwitch.send(2351425, 24); 
  delay(10000);  
}

Para ejecutarlo necesitaremos un emisor de 433 MHz. en este caso ademas de a 5V y GND conectaremos el pin por el que envía los datos al pin digital 10 de la placa de Arduino.

Puedes ver un vídeo con un ejemplo de su uso y más información en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Crear y modificar imágenes con DALL-E

DALL-E no solo permite crear imágenes a partir de un texto. Se pueden modificar y extender. La propia web tiene herramientas para hacerlo.

Pero como es algo que es más fácil de explicar visualmente que por escrito, podéis ver cómo hacerlo explicado en mi siguiente vídeo de mi canal de Youtube:

Haz click en la imagen para ver el vídeo en mi canal de Youtube

Comprobar lo rápido que ordena Arduino

Vamos a ver cómo medir lo rápido que es capaz de ordenar Arduino un listado de 400 enteros (con más qsort falla en Arduino UNO). Aprovecharemos esto para ver cómo medir el tiempo que le cuesta ejecutar el código en Arduino.

Evitar «interrupciones»

Lo primero es quitarnos de «en medio» cualquier molestia a nuestro código en Arduino

  • Procesos asíncronos como la comunicación por Serial o el Watchdog
  • Interrupciones

En el caso de tener el Watchdog activado debemos asegurarnos de que no salta mientras probamos el bloque de código que queremos medir. Podemos deactivarlo con wdt_disable();

Con Serial debemos vaciar la cache usando Serial.flush(); para volcarla

En el caso de las interrupciones se puede usar noInterrupts(); para evitar que se lancen.

Como medida de seguridad extra podemos añadir un delay para dar tiempo a que todo termine. Aunque es una medida más paranoica que otra cosa debido a que Arduino es una arquitectura de un solo hilo (a diferencia de los procesadores modernos que tienen múltiples hilos). No hay manera de que quede alguna «tarea pendiente». Pero, al menos yo, me quedo más tranquilo.

En el caso de nuestro ejemplo no tenemos ni watchdog ni interrupciones, pero si comunicación usando Serial.

Medir el tiempo

Para medir el tiempo debemos crear dos variables de tipo unsigned long una guardara el tiempo justo antes de ejecutar el código y otra justo después. Para saber el tiempo total de ejecución del código basta con restarlas. Si el codigo le cuesta ejcutarsemenos de 70 minutos podemos usar micros() si se cuesta mas millis().

Ejemplo

Veamos el ejemplo comentado al principio midiendo el coste de ordenar 400 elementos:

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}
int cmp_asc(const void *c1, const void *c2){  
  return *((int *)c1) - *((int *)c2);
}
void setup() {
  Serial.begin(9600);
  Serial.println("Start");
  Serial.flush();
  int array[400] = {32, 162, 26, 82, 85, 220, 157, 149, 142, 255, 160, 66, 151, 37, 204, 0, 242, 92, 34, 24, 124, 14, 19, 173, 187, 160, 223, 225, 59, 151, 237, 245, 125, 35, 143, 125, 210, 53, 129, 136, 126, 174, 88, 138, 17, 132, 68, 116, 154, 7, 77, 31, 167, 59, 147, 165, 232, 59, 184, 169, 175, 116, 77, 110, 118, 211, 19, 111, 142, 247, 0, 120, 229, 138, 43, 173, 210, 98, 218, 75, 142, 101, 204, 14, 30, 130, 102, 142, 214, 176, 153, 178, 3, 199, 135, 191, 149, 22, 230, 64, 15, 165, 225, 187, 85, 204, 69, 144, 147, 9, 49, 134, 208, 171, 151, 231, 217, 37, 42, 68, 135, 102, 212, 216, 51, 177, 137, 242, 117, 154, 241, 32, 119, 210, 5, 180, 63, 209, 180, 39, 106, 11, 232, 6, 216, 249, 223, 113, 29, 78, 210, 138, 69, 2, 63, 37, 91, 146, 155, 243, 92, 174, 143, 122, 102, 130, 203, 168, 186, 255, 17, 167, 162, 41, 158, 81, 56, 213, 209, 11, 248, 108, 146, 82, 230, 159, 132, 17, 168, 165, 161, 190, 4, 53, 181, 149, 52, 64, 219, 215, 72, 4, 79, 186, 8, 16, 40, 32, 82, 115, 56, 138, 46, 126, 255, 124, 21, 85, 211, 13, 21, 21, 111, 227, 88, 128, 247, 158, 188, 210, 196, 190, 24, 38, 198, 81, 168, 245, 174, 40, 74, 236, 78, 68, 48, 44, 130, 34, 133, 118, 215, 242, 168, 21, 123, 84, 77, 140, 30, 83, 94, 29, 94, 138, 46, 223, 228, 13, 2, 70, 87, 74, 47, 100, 193, 86, 80, 237, 130, 142, 152, 239, 113, 114, 133, 160, 217, 34, 161, 214, 168, 92, 216, 178, 67, 188, 110, 136, 183, 147, 127, 209, 88, 102, 133, 196, 15, 66, 237, 189, 208, 0, 98, 147, 116, 130, 214, 231, 58, 150, 227, 155, 117, 133, 42, 98, 114, 254, 17, 80, 113, 63, 215, 190, 35, 171, 89, 180, 91, 26, 147, 39, 126, 66, 34, 1, 139, 87, 183, 129, 153, 106, 219, 245, 143, 182, 62, 99, 27, 82, 198, 234, 158, 122, 16, 119, 254, 241, 170, 186, 197, 192, 46, 133, 179, 54, 236, 35, 34, 97, 48, 150, 19, 26, 235, 17, 15, 182, 201, 151, 30, 40, 94, 188, 192, 149, 220, 250, 16, 35};
  unsigned long timeStart;
  unsigned long timeEnd;
  delay(500);
  timeStart = micros();    
  qsort(array, 400, sizeof(int), cmp_asc);
  timeEnd = micros();  
  Serial.print((timeEnd-timeStart));
  Serial.println();
  
}
void loop()
{
}

Puedes ver el vídeo donde ejecuto el ejemplo y muestro sus resultados:

Haz click para ver el vídeo en mi canal de Youtube

Como ordenar un array en Arduino

A veces tras la librerías oficiales de Arduino nos «tapan» utilidades que tienen las librerías avr-libc del fabricante del controlador. en este caso vamos a usar la función qsort() que implementar el algoritmo quicksort. No entraremos en detalle, simplemente aceptaremos que ordena y lo hace rápido.

qsort(void *base, size_t n_memb, size_t size, cmp_t *cmp)

En este caso la función qsort() requiere 4 parámetros:

  • *base : array de elementos a ordenar
  • n_memb : número de elementos del array
  • size : tamaño de cada elemento
  • cmp : función que realiza la comparación

Como qsort no sabe mágicamente como quieres ordenar lo elementos del array hay que pasar una función de comparación entre dos elementos que tiene que devolver los siguientes valores:

  • Si p1 == p2 devuelve 0
  • Si p1 va antes de p2 devuelve -1 (o culaquier número negativo)
  • Si p2 va después de p1 devuelve 1 (o culaquier número positivo)

La función comparación tiene la sigueinte firma:

int cmp_desc(const void *c1, const void *c2)

Como los parámetros se pasan como un puntero (a void) hay que realizar un cast al tipo de elemento de nuestro array. En nuestro ejemplo int, veamos el código de ejemplo de la función ordenar descendente (primero los números mayores)

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}

Veamos un ejemplo completo con ordenación ascendente y descendente:

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}

int cmp_asc(const void *c1, const void *c2){  
  return *((int *)c1) - *((int *)c2);
}


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

  int array[10] = {10, 5, 34, 76, 7, 6, 5, 23, 2, 42};
  
  qsort(array, 10, sizeof(int), cmp_asc);
  
  Serial.println("Resultado ascendente: ");
  for(int i = 0; i < 10; i++){
    Serial.print(array[i]);
    Serial.print(", ");
  } 
  Serial.println("");

  qsort(array, 10, sizeof(int), cmp_desc);

  Serial.println("Resultado descendente: ");
  for(int i = 0; i < 10; i++){
    Serial.print(array[i]);
    Serial.print(", ");
  } 
  Serial.println("");
}


void loop()
{
}

Y así de sencillo podemos ordenar cualquier array siempre que podemos hacer una función que compare valores. No necesitamos ningún tipo de librerías externas.

Puedes ver como funciona este código en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Autómatas celulares y evolución

Vamos a usar los autómatas celulares para modelar una versión muy simple de evolución y luego jugar con ello.

Para simular la evolución necesitamos «algo» en cada individuo (celda) que haga la función de código genético que será sometido a un proceso de selección competitiva según lo adaptado que este al entorno. Los más adaptados tendran más oportunidades de replicarse, pero durante la misma se verán sometidos al cruce con el código genético de otro individuo y a mutaciones.

Veamos en detalle cada una de estas parte.

Genes, genotipo y fenotipo

Vamos a empezar a describir los genes de nuestro individuo. En nuestro caso cada gen es un único bit, por lo que puede tener 2 valores 0 y 1. Cada individuo tiene 8 genes por lo que entre todos serán un único byte. Hay 256 posibles combinaciones de genes (genotipos). Para representar el aspecto (fenotipo) de cada individuo usaremos el nivel de gris representado por el valor del byte que representa sus genes. Este color ira desde negro para el valor mínimo (0) a blanco para el valor máximo (255), pasando por distintos tonos de grises.

Función fitness

Es la función que devuelve un valor que puntúa lo bien adaptado que está un individuo al entorno. Tenemos todo un artículo dedicado a ella.

En este caso tendremos dos funciones fitness para poder jugar con ellas. Para ambas tomaremos el valor del bit representado por los genes. Por ejemplo, el genotipo 00000010 valdrá 2 y el genotipo 00000110 valdrá 6. Esto hace que no todos los genes tengan la misma importancia. También jugaremos con eso.

Para lo que no tengan claro como funcionan los numeros binarios pueden mirar la siguiente tabla para saber cuanto vale cada gen (el valor total del genotipo seria la suma de los valores de todos los bits que estén a 1)

Nº de bit76543210
Valor1286432168421

Hemos dicho que habrá dos funciones fitness una considera como más aptos a los individuos con mayor valor en sus genes. La otra a los individuos con menor valor. Ya veremos cómo jugamos con esto.

En la interfaz está la opción de cambiar la función fitness. Hay tres posibilidades: higth, que da preferencia a los bits de mayor valor; los, que da preferencia a los de menor valor y two, que usa ambas funciones fitness

Selección

Para que haya evolución necesitamos un proceso de selección, en cada iteración cada uno de nuestros individuos elegirá a uno de sus vecinos para cruzarse con él. La selección se hará con un mecanismo de ruleta, es decir, al azar, pero cada vecino tiene diferente probabilidad de salir elegido según el resultado de la función fitness. Los más adaptados son más probables.

Cruce

Una vez seleccionado el otro progenitor, se realiza el cruce de sus genes. El proceso consiste en tomar los 4 bits superiores del individuo, los 4 bits inferiores del vecino y con unirlos para formar el genotipo de descendiente. Esto creará un individuo con un código genético nuevo, mezcla de ambos individuos, que ocupará el lugar de su progenitor.

Mutación

Añadiremos mutaciones, cada cruce tiene cierta probabilidad de sufrir una mutación en uno de sus genes. Al producirse una mutación se cambia el valor de ese gen. Si es 0 pasa a ser 1 y viceversa.

La interfaz permite elegir la probabilidad de que está mutación ocurra en cada cruce. Se puede ajustar su valor entre 0 y 5% aunque lo recomendable es en 0,01% y 0,05%.

Creando el mundo

Cada uno de los individuos del mundo se crean con un valor entre 0 y 127. Esto se hace así para que haya un gen, el de mayor peso (gen 7), que este a 0 en todos ellos. La idea es que ese gen solo se puede poner a 1 a través de una mutación. Visualmente significa que solo puede haber cuadrados entre negro y gris claro. Para que haya cuadrados blancos tiene que producirse una mutación.

Jugando con todo esto

Empecemos por un punto importante, hemos dicho que, al principio, ningún individuo tendrá el gen 7 a 1. De tal manera que si tenemos individuos blancos será gracias a una mutación. Así que si iniciamos el autómata como mutaciones a 0 y fitness a «Hight» veremos que no es capaz de alcanzarlo, si añadimos unas pocas mutaciones lo alcanza sin problemas.

Gen de mayor peso a 0: Con mutaciones – Sin mutaciones

Ahora teniendo todo en blanco y con mutaciones vamos a cambiar la función fitness a «Low» vemos que cambia a negro, los individuos han sido capaces de adaptarse al entorno. Probemos los mismo, partiendo del estado con la mayoría de los individuos blancos, pero con las mutaciones a 0, el resultado es que nada cambia. Sin mutaciones y una población altamente especializada no hay variedad genética como par adaptarse al entorno.

De Hight a Low: Sin mutaciones – Con mutaciones

Veamos otro caso distinto probemos fitness a «Two» que indica que hay una función fitness distinta para cada zona del tablero. Al ejecutarlo veremos un fenómeno que se llama «especiación» en el cual a partir de una especie se crean dos separadas genéticamente.

Especiación

Ya hemos dicho antes que la incapacidad de adaptarse a un entorno es por la falta de variedad genética, al estar en un entorno dominado por muy poca variedad de genes y suprimir la mutaciones como fuente de variación la especie queda atrapada y no puede adaptarse. Pero en el caso anterior tenemos el tablero dividido en dos, hay variedad, aunque suprimamos las mutaciones debería de poder adaptarse y así es. Es capaz de adaptarse a cualquier cambio en el entorno, pero solo una vez.

¿Qué pasa si nos vamos al lado contrario aumentamos mucho las mutaciones?. Que vemos que los ganes varían tanto y tan rápido que es imposible mantener la población estable. Pensad que el simulador solo deja poner una tasa de mutación del 5% (1 de cada 20 individuos tiene una mutación) que parece una tasa baja y aun así se puede ver visualmente como afecta e impide que la población se adapte completamente al entorno.

Podéis probar vosotros mismos aquí y si queréis echar un vistazo al código fuente podéis hacerlo aqui.

Puedes ver un vídeo con más explicaciones en mi canal de Youtube:

Haz click para ver mi vídeo en Youtube

Stable Diffusion se enfrenta a un examen de dibujo.

Ya hemos visto a DALL-E enfrentarse a varios retos. Hoy enfrentamos a otro modelo, está vez código libre, Stable Diffusion. Le he pedido a un profesor de dibujo que le ponga un par de ejercicios y valoremos el resultado.

Ejercicio 1: Mándala

El enunciado del ejercicio es:

Crea un zendala. Elige una gama cromática de colores fríos o cálidos y crea una armonía cromática con colores afines. Usa un mínimo de dos colores y un máximo de tres.

Cambiamos «crear» por «dibuja» y «zendala» por «mandala», palabra mucho más conocida. Lo traducimos al inglés:

Draw a mandala. Choose a chromatic range of cold or warm colors and create a chromatic harmony with similar colors. Use a minimum of two colors and a maximum of three.

Y el resultado es:

Varios puntos a tener en cuenta:

  • Simula ser un dibujo a mano, coloreado y es un mandala. Lo damos por válido.
  • Escala cromática. Falla, no es lo que le hemos pedido, lo intenta pero no sabe que hacer. Incorrecto.

Ejercicio 2: Camaleón puntillista

El siguiente ejercicio tiene dos partes  (iba a ser solo una pero el profesor sugirió un cambio)

Dibuja un camaleón con puntillismo en una jungla

Nos encontramos con la duda de ¿Cuál es la traducción de puntillismo? Google sugirió «pointillism» no estábamos seguros pero continuamos. El texto quedó:

draw a chameleon with pointillism in a jungle
  • Lo esperábamos en blanco y negro, pero no sé lo especificamos. El profesor da por bueno el resultado.
  • Solo es puntillista el camaleón, no la jungla. Puede ser culpa nuestra por no indicarlo ya que puntillismo hace referencia al camaleón
  • Falta un «trozo» de camaleón. Algo habitual en este tipo de IA, no se les da muy bien encuadrar la imagen

La pega es que el profesor esperaba un dibujo en blanco y negro, por lo que había que especificarlo en el prompt. Además pidió otro cambio. No se fiaba mucho de que el algoritmo no estuviera «copiando» imágenes. Por lo que en lugar de en la jungla, algo típico para un camaleón, pidió algo diferente, una escalera.

Cómo cuando usamos «stairs» dibujo un camaleón con un lápiz en la mano ¿? Usamos la palabra «ladder».

Dibuja un camaleón con puntillismo bajando una escalera. Blanco y negro
draw a chameleon with pointillism going down a ladder. Black and white
  • El profesor le da el visto bueno y lo considera como superado
  • Yo señalo que no esta «bajando la escalera». Pero no parece tener importancia

Resultado:

No es fácil dar una nota numérica ya que por un lado los dibujos son espectaculares, por otro comete errores muy simples como el usar más de tres colores en el mandala o cortar al figura del camaleón. El resultado seria claramente más que aprobado aunque no llegaría a sobresaliente.

Puedes ver la explicación en vídeo en mi canal de Youtube:

Haz click para ver el vídeo en Youtube