Dinero como función de utilidad

Los agentes inteligentes necesitan alguna forma de comparar resultados para basar sus decisiones. Es decir saber de forma numérica «lo útil» que es cada decisión. Es algo complicado. Muchas veces podemos estar tentados en tomar atajos. Uno de los más habituales es usar el valor económico como media de este valor. Sin embargo los seres humanos tienen un relación curiosa con el dinero.

¿Por qué es importante el dinero para la I.A.?

El dinero es importante para la I.A. por el sencillo motivo de que es importante para los humanos. Si necesitamos que los agentes inteligentes interaccionen con nosotros han de tener en cuenta nuestras peculiaridades.

También resulta curioso comparar como actuamos con el dinero. El dinero actúa como una función de utilidad para nosotros y al ser tan fácilmente interpretable (es un simple valor numérico) podemos comparar como actuamos respecto a cómo esperaríamos que actúe una inteligencia artificial.

¿Eso quiere decir que algo falla en nosotros o en los agentes? No, simplemente quiere decir que hemos llegado por caminos distintos y valoramos el dinero de forma diferente. Para los agentes es simplemente un número que usar como función de utilidad, mejor cuánto mayor sea el número. Para nosotros todo es más complicado. El dinero es una mezcla de algo natural y conocido como es el acumular bienes materiales con algo nuevo y desconocido que es el valorar algo abstracto e inmaterial como es el dinero.

Los humanos entendemos que acumular bienes es beneficioso, pero en la naturaleza la mayoría de los bienes importantes no son acumulables más allá de cierto punto. La comida se estropea y tener cien lanzas en lugar de dos o tres solo te supone una carga. De hecho repartir las cosas que te sobra entre tus amigos y familia puede ser una buena inversión ya que te conseguirá su simpatía y cierto estatus social y quién sabe si en un futuro necesitarás un favor y te lo podrán devolver. Además ayuda al grupo y estar en un grupo fuerte también es beneficioso. Al final compartir puede resultar la mejor forma de usar los bienes y el reconocimiento del grupo más valioso a largo plazo que los bienes.

Sin embargo. El dinero puede acumularse sin límites, sin suponer una carga o que se eche a perder con el tiempo (se devalúa pero no es comparable). El dinero permite mejorar nuestra calidad de vida y nos proporciona seguridad para el futuro. Pero esta claro que no sabemos muy bien como valorarlo de forma intuitiva lo que da resultado a comportamientos contradictorios.

El valor del dinero y la justicia

El valor del dinero es relativo, depende de lo que tengas, de lo que tengan los que te rodean o incluso tus esperanzas de beneficio. De primeras todos podemos pensar que si sales ganando dinero has ganado. De hecho las matemáticas nos apoyan. Ganar dinero es mejor que no ganarlo y desde luego mucho mejor que perderlo desde la teoría de juegos está claro. Sin embargo para el ser humano no es tan simple.

Hay un juego llamado «ultimátum», sus reglas son sencillas: Hay dos jugadores, a uno se le da una cantidad de dinero, supongamos 100€, su función es decidir cómo repartirlo, cuánto se queda el y cuánto el otro jugador. El otro jugador solo tiene una función, decir si acepta o no el trato. Si lo acepta ambos se quedan con su parte del dinero, si no lo acepta los dos pierden su dinero. Lo lógico sería aceptar cualquier trato cuyo valor sea mayor que 0. Algo siempre es mejor opción que nada. Pero los humanos no somos tan simples y tendemos a rechazar repartos que no sean lo suficientemente «justos», parece que podemos asumir cierta cantidad de injusticia a cambio de un beneficio pero «sin pasarse». Es decir mucha gente estaria dispuesta a aceptar un reparto 60-40 pero muy poca un reparto 90-10.

Para entenderlo vamos a comparar dos casos, supongamos que alguien te regala 10€, estarás agradecido. Ahora estás jugando al juego del ultimátum y te ofrecen 10€. El valor intrínseco del dinero es el mismo. Pero nuestra forma de valorarlo no. La injusticia del reparto hace que el dinero pierda valor, tanto como para llegar a rechazarlo. Ojo que al actuar así nos aseguramos que el reparto se acerque a lo justo. Incluso en el juego del dictador, una versión en la que el segundo jugador no puede decir que no, el reparto se aleja más de lo que sería justo pero no llega a los 0€ que sería lo óptimo en este caso. Esta claro que valoramos algo más que el simple beneficio económico

Se suele decir que «todos tenemos un precio», no estoy seguro si todos lo tenemos, pero algunos desde luego. Si el ultimátum se jugará con millones de euros estoy seguro que mucha gente se trataría su orgullo y aceptaría un reparto de 99 millones a 1 millón aunque fuera injusto.

Valor del dinero en el entorno social

Hay estudios que demuestran que la gente esta más o menos satisfecha con su salario o su bienes en relación a lo que tengan la gente que le rodea. Es decir valoras el dinero como indicador de estatus social. Y aprovechándose de eso muchos productos caros se publicitan como indicadores de estatus social. Se venden como si fueran la cola de un pavo real.

Mucha gente paga más de lo que corresponde en la relación beneficio/costo solo por aparentar. Comprarse un producto caro que no va a aprovechar. Se podría discutir que se compensa por el incremento de estatus social. También se podría cuestionar lo beneficioso que es entrar en esas dinámicas absurdas donde el único beneficio real lo saca el vendedor.

El hecho que haya gente que por aparentar se endeude quiere decir que valoramos el reconocimiento social como una valor en si mismo

Valor del dinero y necesidad

Para hacer un algoritmo que valore lo beneficiosa económicamente que es una acción podemos usar cálculos como la esperanza que son independientes de tu situación, solo dependen del beneficio que se pueda esperar.

En los humanos el valor que damos al dinero depende de la necesidad que tengamos de él. A mayor necesidad mayor valor le damos al dinero, sobre todo si es inmediato. De ello viven esas empresas que dan créditos rápidos a intereses altos. Cuando necesitamos dinero no valoramos correctamente los costes de obtenerlo. De hecho cuando la deuda alcanza un nivel que es imposible de pagar mucha gente opta por adoptar el punto de vista «de perdidos al río» tomando decisiones arriesgadas que pueden hacer que la deuda crezca ¿Pero que más da si ya no podemos pagarla?

Cuando tienes mucho dinero también resulta difícil evaluar el riego y eso hace que mucha gente acabe perdiendo su fortuna. Se da en muchos casos de gente que le ha tocado mucho dinero en la lotería que lo gestiona mal y lo pierde en la llamada maldición de la lotería. Posiblemente debida a que según vas acumulando cosas la felicidad que obtienes de ello disminuye muy rápidamente y acabas gastando más de lo debido. Parece que el acumular cosas por encima de un limite no produce más felicidad.

¿Ocurre solo con el dinero?

Realmente no, ocurre con cualquier cosa que tenga valor para nosotros. Estudios con primates han demostrado comportamientos parecidos con frutas o juguetes. Parece ser que valoramos más cosas que el simple beneficio material de nuestras acciones. Que la justicia, el reconocimiento o el grupo social nos interesa más de lo que consciente mente percibimos.

Cualquier inteligencia, artificial o no, que quiera vivir en la sociedad humana ha de tener en cuenta estos valores o no logrará predecir nuestro comportamiento. Como ejemplo, el caso de un colegio que cansado de que los padres llegaran tarde a buscar a los hijos decidió multarlos. Resultado, los padres empezaron a llegar aun más tarde. Al pagar por ello dejaron de sentirse tan culpables por llegar tarde, el coste social fue reemplazado por el económico

Procesar un texto carácter a carácter

Una de las formas más básicas y relativamente simple para procesar texto es el análisis de carácteres uno a uno. Es un sistema computacionalmente muy simple y que se puede ejecutar con muy poco coste, tan poco que yo he llegado a usarlo en un Arduino con una shield ethernet para procesar peticiones HTTP. El proceso consiste en analizar el texto carácter a carácter clasificando cada carácter en uno o varios tipos y aplicar reglas según esos tipo.

Un ejemplo de tokenizador muy simple sería un sistema que clasifica los caracteres en letras (a-zA-Z), números (0-9) y símbolos (el resto).

for char c in text {
  if tipo(c) == tipoAnteriorSimbolo {
     token += c;
  } else {
    guardarToken(token);
    token = c;
  }
  tipoAnteriorSimbolo = tipo(c);
}

Múltiples tipos por carácter.

El sistema anterior puede ser útil para tratar con lenguajes artificiales muy estrictos como como  ser el uso de comandos o protocolos, pero tiene problemas con lenguajes más «humanos». Por ejemplo si tratamos de sacar los números de la frase : «Debe 1.000,00€ desde el día 10 de junio.» obtendremos «1», «000», «00», «10». Cuando lo correcto es: «1.000,00» y «10». Podemos ver en este caso como hay caracteres que no son números pero deberían considerarse como tales «.,» . Una solución es que en lugar de considerar cada carácter de un solo tipo que puedan ser de varios. En este caso los caracteres «.,» pueden ser considerados signo de puntuación y número.

for char c in text {
  if tipos(c).estaEn(tiposAnteriorSimbolo) {
     token += c;
  } else {
    guardarToken(token);
    token = c;
  }
  tiposAnteriorSimbolo = tipo(c);
}

Ventana de caracteres

Cambiemos de caso, ahora miremos el texto: «Quedamos a las 10. No vino». Aquí el problema es que nuestro sistema vera «10.» como un número, vale que luego en un procesamiento posterior podemos eliminar el punto que sobra. Pero también podemos solucionar ese problema aumentando «la ventana» que ve el algoritmo. Actualmente el algoritmo juzga el carácter actual y el anterior, se puede modificar para que además vea el siguiente carácter, de tal forma que disponga de los tipos de los tres caracteres para comparar.

Luego hay que definir reglas. Por ejemplo para que un punto cuente ha de estar rodeado de otros dos números. Si está rodeado de un número y un espacio el punto no forma parte del número.

Ventajas e inconvenientes

La principal ventaja de este algoritmo son los pocos recursos que necesita para ejecutarse. Para casos limitados puede funcionar perfectamente. Por eso puede ser ideal para usar en microcontroladores.

Su inconveniente es que no es suficiente flexible para procesar lenguajes humanos y que las reglas pueden llegar a ser muy complicadas.

Sin embargo en caso de que no puedas procesar algoritmos más complicados puede ser la única posibilidad.

Problemas con procesamiento de lenguaje natural en Español

Todos los lenguajes tienen sus particularidades que resultan problemáticas a la hora de ser procesados por un algoritmo. Pero como yo me he centrado en el español puedo resumir los problemas que me he encontrado a distintos niveles del proceso. Estas son las características más habituales que se atragantan a la hora de procesar el español:

Verbos

Nuestros verbos son un horror para muchos estudiantes y para su procesamiento por las máquinas. Literalmente cientos de formas verbales según el tiempo, número, género, aspecto y modo. Formas compuestas, perífrasis verbales, pronombres clíticos y cuando tienes todas las reglas modeladas descubres la cantidad de verbos irregulares que hay.

Tildes

aquí la «diversión» viene por dos lados. Primero que para algunos algoritmos hay que tenerlas en cuenta, para otros no. Por ejemplo para los stemmer no se tienen en cuenta para saber la conjugación verbal hay que considerarlas. Por otro lado «olvidarse» de ponerlas es muy habitual (pueden usarse como ejemplo cualquiera de mis post) y más ahora que puede ser correcto escribir la misma palabra con o sin tilde.

Ñ

La ñ, la mayoría de los lenguajes de programación y librerías están pensadas para el alfabeto inglés, por lo que la ñ (al igual que las vocales con tilde) es la gran olvidada del alfabeto español.

Flexiones

Dependiendo la función que haga una palabra puede tener distintas flexiones y hay que tenerlas en cuenta todas. Por ejemplo si quiero buscar referencias a colores hay que tener en cuenta la palabras «rojo, roja, rojos, rojos, rojizo, rojiza, rojizas, rojizos»

Las reglas que hay tiene gran cantidad de excepciones y hay que contemplar otros casos como términos cuyas palabras son distintas para cuando cambias de número o genero (toro – vaca), que las reglas aplican pero el significado es distinto (rata – rato) o con múltiples formas para la misma flexión como puede ser los aumentativos (-azo, -ton) y diminutivos (-ita, -illa, -ica) que además pueden cambiar el sentido de la palabra (listo – listillo) complicándolo todo un poco más.

Reglas poco estrictas para formar frases

Libertad al configurar las frases. Nuestras reglas para componer frases no son muy estrictas. Lo mismo podemos decir «El coche rojo de Juan», «Es rojo el coche de Juan».

Variantes

El español está muy extendido por todo el mundo por lo que hay distintas variantes. Pero como vivimos en un mundo muy interconectado es fácil encontrar esas variantes mezcladas, sobre todo en Internet.

Localización en interiores usando redes WiFi

Ya hemos visto que usar la distancia al emisor para posicionarte dentro de casa es muy difícil, al menos sin usar hardware especializado. ¿Nos hemos quedado sin opciones?. Somos gente ingeniosa, no nos quedamos sin opciones. Si no podemos usar la distancia por la gran variación que tiene la potencia de la señal en interiores, le daremos la vuelta al problema y lo usaremos como ventaja. Si a la gran variación que hay en la potencia de la señal le sumamos la gran cantidad de nodos WiFi que hay alrededor nuestro, resulta muy poco probable que en dos puntos que no estén muy próximos se den los mismos valores para todas las redes.

Vamos a empezar por la idea básica y luego desarrollaremos los distintos problemas que surgen en la vida real.

Primero vamos a movernos por nuestra casa midiendo la potencia de las redes WiFi que se reciben en distintos puntos que consideramos interesantes para localizarnos. Obtendremos una lista de tuplas {SSID, potencia} para cada punto.

Una vez tengamos esa base de datos creada vamos a tratar de saber lo «cerca» que estamos de esos puntos sabiendo solo la potencia de los wifi que nos rodean. En este caso «cerca» no se refiere a distancia física en metros si no a lo parecidas que son las mediciones en dos puntos. Aunque no tenga que ver con la distancia física a este concepto también se le llama distancia.

Para medir esta distancia es fundamental saber que valor o valores vamos a usar, en este caso la potencia de la señal del WiFi. Vamos a compararla con las potencias almacenadas en nuestras mediciones anteriores. Para ello tomaremos las potencias medidas en los nodos con el mismo SSID y las compararemos, sumando luego todas para saber como de «próximos» están esos dos puntos.

Siendo Pi[ssid] la potencia en la base de datos para el nodo con ese SSID en el punto i y Pm[ssid] para la potencia medida de ese mismo nodo la distancia seria:

Dist(Pi) = ∑ √ (Pi[ssid] – Pm[ssid])²

O una forma menos exacta pero también computacionalmente menos costosa (y que a mi me a servido perfectamente)

Dist(Pi) = ∑ ABS(Pi[ssid] – Pm[ssid])

Siendo ABS la función que calcula el valor absoluto.

Que la i no despiste, en ambas formulas el sumatorio se refiere a sumar la diferencias de los distintos SSID medidos de cada punto.

Podemos determinar de que punto estamos más cerca comparando estas distancias y quedándonos con aquel punto Pi cuyo valor sea menor.

Para aplicar esta idea dividí mi casa (realmente solo dos habitaciones contiguas y un trozo de pasillo para probar) en una matriz de cuadrados de un metro de lado y establecí comos puntos de referencia el centro (a ojo) de cada cuadrado.

¿Funciono bien? La sorpresa es que, tras corregir problemas de los que hablo más adelantem mucho mejor de lo que esperaba. Buscando los puntos más cercanos me resultaba fácil saber donde estaba. No siempre me daba como más cercano el punto del que más cerca estaba pero nunca se fue muy lejos, así que podía saber mi posición con bastante precisión.

Problemas

Desgraciadamente de la teoría a la practica suele haber un camino lleno de baches. Por lo que siempre surgen problemas para aplicar la teoría tal cual y que hay que resolver.

Nodos que aparecen y desaparecen

Si los nodos WiFi que usas como referencia están siempre encendidos el hecho de que un nodo no esté entre tus mediciones indica que potencia es 0 y es un gran indicador de donde estamos. Pero si usas los WiFis de tus vecinos el que se apaguen a lo largo del día es algo más habitual de lo que parece. Por lo que tenemos nodos que aparecen y desaparecen. Así que si un nodo no esta entre tus mediciones no se puede considerar que su potencia es 0 (aunque lo sea en ese momento) ya que puedes estar falseando los resultados. Mi experiencia es que si no tienes medición de un nodo lo descartes y no lo tengas en cuenta en tus cálculos.

Ruido en las mediciones

Los valores de potencia pueden variar muchísimo al realizar una medición, mi consejo es que realices varias medidas (yo uso 3) y te quedes con la mediana o la moda de las mismas. Es muy importante sobretodo que realices esto al generar la base de datos de los puntos de referencia.

Otro problema es que no todas los nodos se ven sometidos a las mismas variaciones, contra menor potencia tengan en ese punto más propensas son a sufrir variaciones y más amplias son estas. En algunos casos resulta conveniente fijar un umbral mínimo de potencia para tener en cuenta ese nodo.

Variación de las medidas usando distintos aparatos.

Es recomendable usar el mismo «aparato» para generar la base de datos de puntos que el que luego se va a intentar localizar. Las medidas entre mi portátil, mi smartphone y mi placa Node MCU eran suficientemente distintas como para causar errores si intentaba comprar unas con otras

Guardar datos de nodeMCU (o arduino) en la nube usando IFTTT

La idea es usar IFTTT, que permite interconectar varios servicios, para que nos sirva de repositorio de datos de nuestros sistemas IoT desarrollados con arduino, esp8266, nodeMCU o similares. ¡Y además gratis!.

Webhooks

Nuestro sistema enviara una petición HTTP (GET o POST) al servicio de IFTTT para ello hay que crearse una cuenta allí y activar un evento por webhook, que recibirá los datos que le enviemos.

Los webhook de IFTTT solo permiten pasar hasta tres valores que ademas tiene que ser value1, value2 y value3.

Incluyo un ejemplo de enviar un petición con nodeMCU con 3 valores:

 
void sendMsg(){ 
  http.begin("http://maker.ifttt.com/[tu codigo]/alarm/with/key/[pon aqui tu key]?value1=1&values2=2&value3=3"); //HTTP 
  int httpCode = http.GET(); 
  if(httpCode > 0){
    Serial.println("Alarm send"); 
  } else {
    Serial.println("Error send alarm"); 
  } 
  delay(1000); 
}
 


IFTTT recibe un nombre de evento, tres parámetros y el timestamp de cuando ha ocurrido el evento:

{{OccurredAt}}
{{EventName}}
{{Value1}}
{{Value2}}
{{Value3}}

Okey tenemos esos cinco parámetros ¿Que podemos hacer con ellos?

El que incluya la hora del evento es muy útil ya que muchos sistemas no tiene un reloj y no pueden saber que hora es.

Hay que tener en cuenta es si tenemos una sola placa emitiendo datos o tenemos varias. Si tenemos varias podemos usar el nombre del evento o uno de los valores para indicar que placa es, es algo muy útil sobre todo cuando una de ellas empieza a dar datos erróneos así resulta fácil filtrarlos.

Puede parecer que un nombre de evento y tres valores son pocos pero para muchos sistemas de IoT son mas que suficientes.

Hoja de cálculo

Mi primera idea es guardar los datos que nos interese en una hoja de calculo de Google. IFTTT nos permite guardar los datos en filas, con una columna cada datos. Hasta 2000 filas, luego no deja de guardarlas, pero crea otra hoja de cálculo.

La ventaja de este sistema es que podemos conectarlo para que genere tablas, gráficas o realice cálculos de forma automática. Con poco trabajo nos podemos montar un sistema que almacene y procese lo datos generando informes y gráficas todo ello gratis.

Fichero de texto

Otra opción es usar un fichero de texto y almacenar los datos estilo CSV, que consiste en separar los valores por comas y ya esta. Se pueden usar varios, en mi caso opte por Dropbox. La ventaja de este tipo de ficheros es que es muy sencillo de procesar por cualquier dispositivo. De hecho hay tres maneras de hacerlo con Dropbox.

  • Sincronización: tener Dropbox sincronizado en el dispositivo con lo que el fichero de actualiza «solo» con cada cambio
  • API: usar la API de Dropbox para acceder a ese fichero
  • Publico: Dropbox permite crear enlaces públicos a ficheros. Una vez generado ese enlace leer es fichero es tan sencillo como hace una petición GET

En el caso de este tipo de ficheros el limite de IFTTT esta en 2 megas de datos.

Redes sociales

Si los datos han de ser compartidos con otras personas, las redes sociales pueden ser una buena solución, por ejemplo una cuanta de Twitter (privada si no quieres que los datos sean accesibles por todos). Además también se puede automatizar el acceso a los datos usando la API de Twitter

Notificaciones

Si los avisos tienen que llegar en tiempo real (o casi) se pueden usar las notificaciones a móvil (ya hice un ejemplo) o si tienen que llegar a un grupo de gente se pueden notificar en un grupo de Telegram. Este mismo sistema se puede usar para notificaciones personales si quieres guardar un histórico de ellas.

Calendario

Este caso es solo una idea, pero en el caso de eventos que ocurran solo de vez en cuando y se quiera tener un registro de cuando ocurrieron se podría usar la integración con alguno de los calendarios.

No resulta útil para eventos muy numerosos ya que es difícil de consultar cuando hay muchos en un día

¿Es inteligente todo lo que parece inteligente?

Todos hemos oído hablar del test de Turing u otras pruebas para medir la inteligencia de los agentes inteligentes. La habitación china plantea un escenario que reta la validez de estos test. Voy a explicarla de una manera diferente a la habitual pero que creo que es más sencilla de entender. Una habitación china es una caja donde metes un texto y obtienes una respuesta coherente. Podrida ser un chatbot que superase el test de Turing. Le introduces preguntas y obtienes una respuesta coherente. El comportamiento visto desde fuera es inteligente. ¿Pero realmente lo es? ¿Cómo distinguimos algo que actúa como si fuera inteligente de algo que es inteligente?. (Vamos a dejar de lado que muchas veces solo consideramos inteligentes comportamientos humanos)

Supongamos una inteligencia artificial que funciona de forma similar a la habitación china, pero que en lugar de responder preguntas juega a las damas o al ajedrez. Parar ello no tiene un complicado algoritmo para deducir el siguiente movimiento, tiene una base de datos con todos los posibles movimientos y sus posible respuestas. Para juegos como el ajedrez o el go eso es imposible pero es posible para juegos mucho mas sencillos como el tres en raya, el conecta 4 o incluso algunas versiones dé las damas. Esta IA solo busca la posición actual en su base de datos y nos responde con el mejor movimiento. Desde fuera nos parecerá un jugador realmente bueno y lo clasificaríamos como inteligente, sin embargo no actúa de forma mucho más inteligente que cualquier otra búsqueda en un base de datos.

Si habéis visto análisis de partidas de ajedrez o de go donde alguno de los jugadores es una I.A. veréis que los comentaristas hablan de «estrategia», como los movimientos que se han realizado en momentos anteriores de la partida le han permitido colocar sus fichas en la posición para ganar la partida o tomar ventaja al rival o «asestar un golpe al tablero». El problema es que las maquinas no tienen estrategia de ningún tipo, o al menos no nuestra idea de «tener una estrategia» estos algoritmos en su mayor parte ven el tablero cada vez de manera independiente a las anteriores. Es decir cada vez que tiene que decidir parten de cero, observan el tablero, deciden el mejor movimiento y excepto por datos que se almacenen para no volver calcularlos olvidan todo para su próximo movimiento. Para estas I.A. la partida como tal no existe, una partida es una sucesión de tableros para los que deciden cual es el mejor movimiento sin pensar en los anteriores tableros ni en los siguientes. Lo mismo les daría llegar a mitad de una partida. Esta forma de jugar es inconcebible para un humano, nosotros desarrollamos estrategias e intentamos llevarlas acabo. La I.A. parte del tablero actual, simula posibles movimientos, valora posiciones y elige aquel que piensa que le da más probabilidades de ganar y olvida todo eso en el siguiente movimiento.

Ahora mismo tenemos algoritmos que serian capaces de trazar un plan y que la I.A. elija sus movimientos para cumplir ese plan. Esa forma de actuar Seria más  apreciada a la forma de los humanos e crear una estrategia, sin embargo no se usan para este tipo de juegos porque no dan tan buen resultado.

¿No podria pasar lo mismo con la inteligencia de lo que nos pasa con estrategia? ¿Qué algo nos parezca inteligente pero no es nada más que una interpretación que hacemos nosotros de forma engañosa? Y volvemos a la habitación china y a su maquina que no piensa, solo busca respuestas de una enorme base de conocimientos. Que actúa de forma que parece (o es) inteligente.

¿Cuál es la definición de inteligencia? Pues sorprendentemente (o no) no existe un única definición, no se tiene muy claro ni el alcance de lo que es inteligencia. La inteligencia parece recoger diferentes fenómenos y capacidades. Gente que decimos que es inteligente: si entiendes textos complicados, si resuelves problemas matemáticos complejos, si eres bueno en algún aspecto técnico, si eres de respuesta rápida, si tiene una lenguaje muy rico, si tienes bastos conocimientos…. hay ejemplos para aburrir. Nos resulta fácil decir que es inteligente, pero no definirlo.

Incluso a nivel humano cuesta, por ejemplo el cubo de Rubik. La primera vez que te dan un cubo de Rubik y tratas de resolverlo te sorprende que haya gente que sea capaz de resolverlo, tienen que ser tremendamente inteligentes. Cuando investigas un poco, ves que hay técnicas para resolverlo y que en muchos casos es más un asunto de memoria y habilidad. De repente algo que nos parecía sin duda inteligente ha perdido parte de «inteligencia». Sin embargo parte de ser un gran jugador de muchos juegos necesitas aprender de memoria gran cantidad de movimientos y posiciones para no tener que pensarlos de cero cada partida.

 Si vemos la inteligencia más como un fenómeno, un resultado de las acciones, cualquier cosa que parezca inteligente sería inteligente. Daría igual que supiéramos resolver cubos de Rubik gracias a nuestro razonamiento o a nuestra memoria. En ese caso el test de Turing volvería a ser válido. Es decir la habitación china nos obliga a plantearnos si vamos considerar inteligente todo lo que se comporta de forma inteligente o si vamos a más requisitos. teniendo en cuenta que ni siquiera estamos seguros de nuestra inteligencia o nuestro libre albedrío yo no seria muy exigente.

Comparar la inteligencia artificial con la humana

También podría titularse «Porqué no paran de anunciar nuevos logros en I.A. pero a mí me siguen pareciendo igual de tontas». Pese a que cada poco tenemos una noticia de algún avance increíble y coletillas de lo cerca que está la singularidad. Actualmente ninguna I.A. sería capaz de sobrevivir a cosas como «levantarse e ir a trabajar». Si, nos ganan al ajedrez, al go, nos apalizan a las damas o nos hunden jugando a videojuegos. Cosas que a nosotros nos cuestan sudor y lágrimas las máquinas las realizan en milésimas de segundo. El problema es que estamos infravalorándonos y no sabemos apreciar la cantidad de cosas dificilísimas que hacemos sin darnos cuenta y sin coste aparente. No penséis que esto es nuevo, tiene nombre, la paradoja de Moravec .

Hay que tener en cuenta que todas esas cosas que nos parecen tan complicadas como derrotar a maestros del ajedrez realmente no lo son tanto. Es solo que nuestro cerebro no ha evolucionado para jugar al ajedrez. Pero sí que ha evolucionado para reconocer objetos, mantener conversaciones, trazar planes y montón de cosas que hacemos sin esfuerzo y por eso no lo sabemos valorar. Sin embargo los ordenadores se crearon para agilizar los cálculos y son buenos en ello. Es sorprendente lo lejos que han llegado siendo tan relativamente simples. La I.A. es un logro de las matemáticas más la capacidad de cálculo alcanzada por el hardware.

En los comienzos de la inteligencia artificial se subestiman los costes de simular a los humanos. En unas pocas décadas se iba a lograr crear máquinas inteligentes, apenas unos pocos años en crear máquinas que mantuvieran conversaciones y superarían el test de Turing y el problema de la visión por computador en un verano estaría resuelto. La realidad resultó más dura de lo esperado y con el paso de los años nos hemos dado cuenta de que realizamos con facilidad tareas que realmente son tan complicadas que no sabemos ni como definirlas. ¿Cómo explicamos la capacidad humana de reconocer objetos y poder integrarlos en un contexto? La visión por computador sufre para separar los objetos de una imagen y reconocerlos todos y nosotros reconocemos las cosas con un vistazo incluso si están parcialmente ocultas y no solo eso, integramos toda la información para hacernos una idea del entorno.

Imaginad que dejamos a una máquina en una ciudad que no conoce de un país en el que nunca ha estado y donde hablan un idioma que no entiende. Su única ayuda es un mapa no muy exacto y una guía de viajes no demasiado extensa. Su misión es llegar al hotel y organizar un plan para visitar la ciudad en unos pocos días, sin olvidar lograr comer y dormir todos los días. ninguna inteligencia artificial esta ni cerca de lograr hacer eso, pero nosotros lo llamamos «vacaciones».

Además no hay que olvidar que en el caso de la I.A. la fuerza bruta sigue teniendo mucho que ver. Para que una red neuronal reconozca objetos necesita entrenar con miles de ejemplos de lo que queremos que reconozca y de lo que no. Para que gane a cualquier juego necesita muchos miles de partidas. Aunque nuestra capacidad de aprendizaje llega mucho menos lejos que las de la I.A. nuestra capacidad de aprender es muy superior necesitamos menos ejemplos y nuestro aprendizaje es más flexible.

En resumen, aunque nos parezca que la I.A. hace cosas increíbles es solo porque no sabemos valorar correctamente lo que nuestro cerebro hace con engañosa facilidad. Aun estamos lejos de darle a la I.A. esas capacidades. Quizás el problema no es que no sepamos valorar los avances realizados en I.A., asombrosos sin duda alguna, si no que no sabemos valorar nuestras capacidades naturales de lo acostumbrado que estamos a ellas.

Diagrama de Voronoi con autómatas celulares

Vamos a usar autómatas celulares para calcular el diagrama de Voronoi de varios puntos. El diagrama de voronoi se calcula a partir de una serie de puntos P[i] en un espacio (en este caso un plano). A partir de cada uno de esos puntos P[i] crea uno polígono según la siguiente regla: un punto del plano pertenecerá a un polígono si no hay ningún otro punto P[i] más cercano. Las fronteras entre estos polígonos (Polígonos de Thiessen) son el diagrama de Voronoi.

<Voronoi diagram.png
De Gottie (fuente Wikipedia Image:Voronoi.png), Dominio público

En este las reglas del autómata son sencillas, si un vecino tiene una distancia menos que la propia, copia su color y su distancia + 1.

for(var i = 0; i < neighbors.length; ++i){
   if(neighbors[i].creature.distance < this.distance){
     this.distance = neighbors[i].creature.distance+1;
     this.point = neighbors[i].creature.point; 
} } 

Queda inicializar las celdas, para ello una celda puede ser dos tipos:

  • un punto a partir del cual calcular las fronteras o una celda normal, su distancia es 0 y se inicializan con un color de una lista.
  • una celda nomal, se inicializan sin pertenecer a ningún polígono, con color blanco y un valor de distancia muy grande (lo ideal seria infinito pero con que sea mayor que cualquier distancia entre dos puntos de la rejilla de celdas también sirve)
if(this.isPoint){
  this.distance = 0;
  this.point = initPointsNumber;
  initPointsNumber++;    
} else {
  this.point = -1;
  this.distance = 10000;
}

El funcionamiento del autómata es muy parecido al algoritmos de inundación las distancias menores se van propagando junto con el color hasta que encuentran un vecino cuya distancia es menor, en es punto es donde se encuentra el borde del polígono.

Tenéis el código aquí y un ejemplo aquí, el resultado es:

Si os fijáis bien parece que la imagen está cortada por los extremos, eso es porque en este caso no estamos calculando el diagrama de Voronoi en un plano si no en un toro (que es la forma geométrica del mundo de este autómata) por eso los polígonos cuando salen por un extremo continúan por el otro.

Puedes ver un vídeo sobre como funciona en mi canal de Youtube:

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

Autómatas Celulares

Para definir lo que es un autómata celular vamos a empezar por su versión más simple y vamos complicando el modelo. Empezamos por el más simple, un cuadrado que puede tener varios estados y cada estado está asociado a un color. El cambio de estados está regulado por un conjunto de reglas. Por lo que cada autómata queda definido por sus estados y las reglas que los cambian. A los autómatas de les conoce como celdas, individuos o células

Una dimensión

Siguiente nivel de dificultad los autómatas con vecinos a ambos lados. Son los autómatas unidimensionales. Se distribuyen en forma de línea. Cada autómata tiene dos vecinos que son otro autómata idéntico situado a la derecha e izquierda. Pero cómo sería muy aburrido tener muchos autómatas que cambien de estado sin interaccionar entre ellos ahora las reglas determinan el cambio según el estado propio y el de los dos vecinos.

El caso más estudiado de este tipo de autómatas celulares es el de celdas de dos estados: blanco y negro o 0 y 1 o muerto y vivo. Las reglas de cambio se expresan como tres celdas. La del centro representa el estado de la celda actual y la de la derecha e izquierda sus dos vecinos. El valor que tomara la celda central se representa debajo. Las reglas toman forma de figura del tetris. Por ejemplo dos formas de expresar la misma regla:

111110101100011010001000
0 1 0 1 1 0 1 0
regla90
Dos formas de expresar la misma regla

Esta regla es conocida como la regla 90. ¿De donde sale ese 90? . Muy sencillo, de la segunda fila de la tabla. En binario 01011010 = 90. Cada regla se denomina así, por el número decimal que se obtiene de poner en decimal los resultados de la regla de transformación. Como solo hay 8 posibles reglas (hay ocho posibles configuraciones de las tres celdas) solo hay 256 reglas.

Los autómatas unidimensionales se representa como una linea. Cada paso en su ejecución se representa como una linea nueva que se pone debajo de la anterior, formando un plano.

Regla 90 tras varios pasos

Dos dimensiones

Aumentamos una la dimensión vamos a los autómatas bidimensionales. En este caso hay 4 u 8 vecinos dependiendo que tipo de vecindad se use. La vecindad de Von Neumann solo tiene en cuenta los vecinos situados arriba, abajo, a la izquierda y a la derecha, mientras que la vecindad de Moore tiene en cuenta también los vecinos de las diagonales.

Vecindad Von Neumann y Moore

En este tipo de autómatas, por lo general, para las reglas de cambio de estado se tienen en cuenta el número de vecinos en un determinado estado. El ejemplo más famoso de este tipo es el juego de la vida que intenta emular una especie de ecosistema en un autómata muy simple cuyas reglas son:

  • Si una ceda está muerta y tiene 3 vecinas vivas, cambia a estado viva
  • Si una celda está viva y tiene 2 o 3 vecinas vivas, sigue viva
  • Si una celda está viva y tiene más de 3 vecinas vivas, cambia a estado muerta

Hay casos en que no solo importa el número de vecinos en un estado, si no también su posición en el espacio (arriba, bajo, izquierda, derecha, …). Un caso habitual es cuando se simulan fuerzas

Tres dimensiones

Hay autómatas celulares de más dimensiones, si bien tienen utilidad, por ejemplo en creación de escenarios de forma procedural o en ciertas simulaciones, no son tan conocidos debido a que empiezan a alcanzar un nivel de complejidad bastante alto y se pierde una de las ventajas de los autómatas celulares de una y dos dimensiones: lo fácil que es visualizar la evolución de los mismos. Ademas mayor número de dimensiones no aporta demasiado desde el punto de vista de «experimentar» las dos dimensiones son suficientes.

Fronteras

Uno de los problemas que tienes al diseñar un autómata es que haces con las celdas que están en los bordes (fronteras) del sistema. No tiene el mismo número de vecinos que las demás. Hay varias soluciones:

  • Valores fijos: se simula que más allá de las fronteras hay otras celdas con valores fijos
  • Mundo infinito: el autómata crece en cada iteración generándose nuevas celdas en los extremos. El problema de esto es que necesitas una cantidad enorme de memoria (idealmente infinita) para que funcione.
  • Mundo circular: se simula que los extremos están en contacto entre ellos, el superior con el inferior y el izquierdo con el derecho, por lo que no hay fronteras y el mundo es cerrado. Es la solución más habitual. Adoptando la forma de un cilindro en los autómatas unidimensionales y de un toro en los bidimensionales

Variaciones

Hemos explicado los ejemplos más sencillos vamos a ver algunas variaciones sobre estos ejemplos:

Más estados. Es habitual los autómatas celulares con múltiples estados.

Estados intermedios: Hay casos en que no solo hay estados «completos» por ejemplo «vivo o muerto», un estado puede representarse como un estado intermedio entre los dos. Por ejemplo si muerto tiene el valor 0 y viva el valor 1 tener un estado 0.5.

Múltiples características: Puede ser que una misma celda pueda tenga distintas «características» cada una de ella en con un estado distinto. De tal forma que una celda se convierte en un conjunto de estados diversos que pueden estar o no relacionados.

Reglas estocásticas. Hasta ahora hemos visto reglas que calculan el estado que toma la celda de manera determinista, pero esto no tiene porque ser así, puede ser que la regla determine solo la probabilidad del cambio de estado.

Diferentes vecindarios. Ya hemos visto que hay dos vecindades tradicionales la Moore y la Von Neumann ¿Pero que pasa si queremos aumentar el número de vecinos o su complejidad?. Podríamos aumentar las dimensiones pero ya hemos visto que eso tiene un limite. La solución es ampliar l definición de «vecindad» en lugar de ser solo las celdas en contacto directo podemos usar como vecinos también las de las siguientes hileras. A esto se le denomina el radio de la vecindad.

RadioNº de vecinos
18
224
348
480

Numero de vecinos = ((2*radio+1)^2)-1;

Diferente geometría. Esta relacionado con el punto anterior. En lugar de usar cuadrados se pueden usar hexágonos, triángulos, combinaciones de diversas figuras. En el caso más extremo las relaciones de vecindad se puede representar como un grafo que conecte a los vecinos entre ellos siendo innecesario que tengan una representación geométrica.

Celdas con diferente reglas. Tampoco es necesario tener las mismas reglas para todos los individuos. Puede haber varios grupos de reglas que apliquen a distintos tipos de celdas o incluso que cada celda tenga sus propias reglas generadas como combinación de las suyas propias y de sus vecinos.

La biblia de los autómatas celulares

Aunque encontrar un solo libro que hable de todas las posibilidades de los automatas celulares es imposible debido a la cantidad de usos que tienen una buena obra para introducirse en ellos es «A new Kind of Science» de Stephen Wolfram. Ademas esta disponible para leer de forma gratuita (y legal) en la web Siempre y cuando te defiendas bien leyendo en inglés

Puedes ver el vídeo que hice sobre este tema en mi canal:

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

Simular con autómatas celulares la propagación de una enfermedad

Vamos a ver un ejemplo de lo fácil que es realizar algunas simulaciones simples usando autómatas celulares. En este caso modelaremos la evolución de una enfermedad en una población. Hay modelos matemáticos muy elaborados sobre enfermedades y que desde luego son más realistas que el que vamos a construir (aunque realmente se puede elaborar tanto como quieras).  La ventaja de los autómatas celulares es que son mucho más visuales lo que permite entender el desarrollo del sistema de forma más intuitiva, además sus mecanismos son más fáciles de entender y modificar que un sistema de ecuaciones diferenciales.

Para modelar nuestro autómata celular vamos a usar terra.js

Usaremos un autómata con 8 vecinos, cada celda va a simular ser un individuo diferente en contacto con otros individuos (sus celdas vecinas). Un individuo puede estar en cuatro estados:

  • Susceptible: el individuo no ha sido infectado, pero puede contagiarse si cualquiera de sus vecinos está enfermo
  • Infectado: el individuo esta enfermo, durante unos turnos seguirá en ese estado y será contagioso. Pasados esos turnos pasará a estar recuperado
  • Recuperado: tras pasar la infección el individuo ni es contagioso ni puede volverse a contagiar
  • Inmune: el individuo es inmune a la infección, no puede estar enfermo. Para el caso es como si fuera un recuperado solo que desde antes de infectarse, Sirve para simular inmunes y vacunados.

Esto es lo que se conoce como «Modelo SIR» (Susceptible Infectado Recuperado).

Parámetros

Tendremos que configurar varios parámetros para modelar la enfermedad.

Lo días (ciclos) que dura la enfermedad, illDuration, durante esos días el individuo infectado puede infectar a sus vecinos

Para caracterizar lo «infeccioso» que es el virus usaremos el número reproductivo básico o R0. Podemos interpretar este número como cuantas infecciones causa cada infectado en el periodo que está enfermo. En nuestro autómata celular cada ciclo es un día por lo que el número de individuos infectados por día será (en teoría) R0/illDuration. Necesitamos traducir esto a probabilidades que tiene cada uno de los 8 vecinos de contagiarse Pc = (R0/illDuration)/8. ¿Y qué pasa si R0 es mayor que 8? Es cierto que ningún individuo podrá infectar a más de 8 vecinos, este R= mayor se traducirá en una propagación más rápida de la enfermedad.

config.illDuration = 14; //duration of illness (cicles)
config.pc = (config.r0/config.illDuration)/8;

Cuando un individuo sano tiene como vecino a uno infectado tiene una probabilidad Pc de contagiarse. Esta probabilidad aumenta con el número de vecinos. La probabilidad de infectarse es 1-(1-Pc)^n siendo n el numero de vecinos infectados. ¿De donde sale esa formula?. Vamos despacito, si tenemos solo un vecino la probabilidad de infectarse es: Pc. Pero si tenemos dos vecinos la probabilidad de infectarse es: la probabilidad de infectarse del vecino A, la probabilidad de infectarse del vecino B y la posibilidad de infectarse de ambos. Resulta más sencillo calcular lo contrario, la probabilidad de no-contagiarse de ninguno, que para un vecino es Pnc = 1-Pc. La probabilidad de no infectarse de 2 vecinos es Pnc = (1-Pc)(1-Pc). De tres: Pnc = (1-Pc)*(1-Pc)*(1-Pc). De n: Pnc = (1-Pc)^n. Como lo que queremos saber es la probabilidad de infectarse y lo que hemos calculado es la de no infectarse, la de infectarse debe de ser: 1- Pnc = 1-(1-Pc)^n . Cada turno, cada individuo sano se genera un número al azar entre 0 y 1, si es menor que la probabilidad de infectarse de sus vecinos su estado cambia a infectado.

process: function (neighbors, x, y) {    
  if(!this.isIll && !this.wasIll && !this.isInmune){ //individuo sano 
    //vecinos enfermos alrededor
    var surrounding = neighborsCount(neighbors, 'isIll', true);
    if(Math.random() < 1-Math.pow(1-config.pc, surrounding)){
      this.isIll = true;          
      this.timesIll = config.illDuration;
    }
  } else if(this.isIll){ //individuo enfermo
      ......
  }
  return true;
}

Si un individuo ya esta enfermo le restamos tiempo de enfermedad cada turno hasta que llegue a 0 que es cuando deja de estar enfermo, deja de ser contagioso y pasa a estar recuperado.

process: function (neighbors, x, y) {    
  if(!this.isIll && !this.wasIll && !this.isInmune){ //individuo sano 
    ......
  } else if(this.isIll){ //individuo enfermo
    this.timesIll--;
    if(this.timesIll == 0){
      this.isIll = false;
      this.wasIll = true;
    }
  }
  return true;
}

Para caracterizar la población se puede establecer el tamaño, que porcentaje comienza enfermo y que porcentaje es inmune/vacunado. Al principio de la simulación los individuos se generan aleatoriamente según las probabilidades indicadas:

this.isIll = withProbability(config.startIll/100, true, false);
if(this.isIll){ //enfermo
  this.timesIll = config.illDuration;
} else { 
  this.timesIll = 0;
   //inmune
  this.isInmune = withProbability(config.startInmune/100, true, false);
}
this.wasIll = false;

Indicadores

A parte del tablero del autómata celular contamos con varios indicadores:

El numero de enfermos en ese ciclo

El número de recuperados hasta ese momento

Rt, el valor R0 del que hemos hablado antes solo es valido en el ciclo 0 (de hay el 0 detrás de la R) según la población se va infectando y recuperando el número decrece ya que hay menos población a la que infectar. Rt aproxima el valor de R en ese ciclo se calcula como: Rt = R0 * (1 – (enfermos+recuperados)/población). Cuando Rt es menor que 1 indica que la propagación empieza a remitir, con Rt mayor que 1 la propagación de la enfermedad aumenta.

Inmunidad de grupo (herdImmunity), es el punto en que Rt será menor que 1. Como ya hemos visto a partir de la cantidad de infectados y recuperados se puede estimar que valor de Rt tendrás. Por lo tanto podemos decir Rt = 1 = R0 * herdImmunity => herdImmunity = (R0-1)/R0;

El porcentaje de gente que nunca ha sido infectada, indica el avance de la enfermedad y su extensión.

stats.ills = ills;
stats.noInfectedPer = 100 * (config.population - (ills + recovered))/config.population;
stats.recovered = recovered;
stats.rt = config.r0 * (1 - ((ills+recovered)/config.population))
stats.herdImmunity = (config.r0-1)/config.r0;

El código completo puede verse aquí y una demostración aquí

automata

Puedes ver un vídeo explicativo en mi canal de Youtube:

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