Sunday, February 06, 2011

Midiendo el ánimo del mundo

No veo la tele, pocas veces lo hago, ni siquiera cuando estoy en España. Sólo sintonizo algún canal francés via VLC gracias a mi proveedor de adsl (Free) cuando estoy en Lyon, el cual nos deja los 20 megas a 30 euros mensuales.

La verdad es que con la cantidad de información a la que tenemos acceso estos días prefiero intentar filtrar aquello que pueda ser interesante, sin que ese canal de información sea sólo en una dirección.
Así que hace unas semanas me decidí a medir el ánimo mundial basándome en estudios ya comenzados relativos a ésta materia, registrando los eventos más importantes y dando un clima más cálido a la casa, mezclando hardware y software libre con sentimientos reales de personas.

Para poder llevar a cabo el proyecto era necesario estudiar cuáles son las palabras que identifican mejor los sentimientos, me disponía a estudiar las variaciones de la siguiente lista de sentimientos, la clase Tipo de Animo.

class TipoAnimo:
AMOR=0
ALEGRIA=1
SORPRESA=2
IRA=3
ENVIDIA=4
TRISTEZA=5
MIEDO=6
NUM_TIPOS_ANIMO = 7



Así empezaba la preparación del código Python que se encargaría d e enviar las consultas a Twitter para su posterior procesado.
Algunos ejemplos de palabras seleccionadas para la búsqueda de emociones son las siguientes:


AMOR_QUERY = '\"te+quiero+mucho\"+OR+\"te+quiero+más\"+OR+\"amo+tanto\"+[...]
IRA_QUERY='\"te+odio\"+OR+\"siento+rabia\"+OR+\"le+odio\"+OR+\"estoy+furioso\"+[...]
ALEGRIA_QUERY='\"mas+feliz\"+OR+\"bastante+feliz\"+OR+\"tan+feliz\"+OR+[...]
SORPRESA_QUERY='\"no+me+lo+puedo+creer\"+OR+\"increible\"+OR+\"asombro\"+OR+[...
ENVIDIA_QUERY='\"ambiciono\"+OR+\"codicio\"+OR+\"mucha+envidia\"+OR+[...]
TRISTEZA_QUERY='\"muy+triste\"+OR+\"tan+deprimido\"+OR+\"estoy+llorando\"+OR+[...]
MIEDO_QUERY='\"muy+asustado\"+OR+\"tan+asustada\"+OR+\"realmente+asustado\"+OR+[...]


Teníamos entonces las palabras exactas que nos marcarían las emociones. Adecuándolas para evitar falsos positivos del estilo "quiero comer", para ello utilizaremos palabras con adverbios y pronombres, nos sirven para acotar mejor la búsqueda y seleccionar la emoción en cuestión de una manera más exacta.
A modo de ejemplo y para mostrar la importancia de la selección de las palabras, hace unos días me extraño mucho que durante la etapa de estudio siempre había una emoción que no decrecía en intensidad, durante la noche en América del Sur y las primeras horas de la mañana de España, era el miedo, pensé que igual la noche tuviera algo que ver con esta curiosa actitud...

Hasta que me fijé, en que una de las palabras seleccionadas para medir el miedo era "Horror", y viendo los tweets generados por esa palabra había muchos tweets en inglés, normal que el miedo no durmiera en español, los anglo parlantes lo tenía a su merced durante su zona horaria.

Necesitábamos también una magnitud que nos permitiera la medida de las emociones así utilizamos el número de tweets por minuto que gracias a la fantástica API de búsqueda de twitter, podíamos conseguir con una sencilla función en python:

def parse_tps(animoID):
print 'query_dict[animoID]= '+query_dict[animoID]
#query can be done either json or atom
base_url='http://search.twitter.com/search.json?q='+query_dict[animoID]+'&rpp=30&locale=es&result_type=recent'
f = 0
try:
f = urllib2.urlopen(base_url)
except urllib2.URLError, (err):
print "URL error(%s)" % (err)
if (f != 0):
a = json.loads(f.read())
#debug
#todo keep the msg if somethings happens
b = json.dumps(a, sort_keys=True, indent=4)
first_tw_time = a['results'][0]['created_at']
last_tw_time= a['results'][29]['created_at']
tstart = time_string_to_stamp(first_tw_time)
tend = time_string_to_stamp(last_tw_time)
tps = 30 / (tstart - tend)
else:
print 'We shouldnt be here, as this is bad'
tps= c.all_tpm[animoID] / 60 #If we cannot get value from http we keep the old one
#returning the tweets per second and all the message just in case we have an alert
return tps,b

Una vez registrados los tweets por minuto ya podíamos empezar a estudiar las emociones, mi idea se centraba en saber que ocurría en el mundo en tiempo real, mediante la fase de estudio observaba picos de alguna emoción que hacía que me picase la curiosidad. ¿Qué estaba pasando en twitter?
Pero no tenía tiempo para saberlo, el pasado era pasado, los "timestamps" demasiado ajustados para una posterior búsqueda y el tiempo que requería esa búsqueda no lo tenía y no lo quería.

Tampoco me apetecía que múltiples alertas cortas en el tiempo estuvieran generando alarmas continuamente. Así que necesitábamos aplicar algunas fórmulas matemáticas o de estadística que dieran el peso adecuado a las emociones.

Nos interesa saber qué es una emoción normal en twitter y que es lo que supone un cambio en ese comportamiento, para ello, y viendo resultados de otros estudios, me decidí por utilizar moving averages. Aplicando un factor de suavizado a las medidas registradas, concretamente un factor exponencial que diera una importancia exponencial en el tiempo para las medidas pasadas.

S_{t} = \alpha \times Y_{t} + (1-\alpha) \times S_{t-1}



El ánimo mundial normal irá evolucionando, al igual que la temperatura global del planeta puede cambiar, el ánimo lo hace de la misma manera, como por ejemplo las épocas de glaciación influirán de una manera diferente en la temperatura media o en la temperatura inmediata.

Así en el código diferenciaremos entre emociones, ánimo y temperamento mundial. Las emociones son inmediatas y están basadas directamente en la cantidad de tweets por minuto para un instante determinado. El ánimo se extiende más en el tiempo y finalmente el temperamento del mundo abarca toda la historia del mundo, desde que el código se lanzó a ejecutar, claro.

#aplicamos exponential moving averages
self.animo_mundial_avg[animoID] = self.animo_mundial_avg[animoID] * (1 - a) + tpm * a
#debug print 'timestamp: '+str(self.timestamp)+' animo at T '+str(self.animo_mundial_avg)
for i in range(NUM_TIPOS_ANIMO):
self.ratios_temperamento[i] = self.ratios_temperamento[i] * (1 - a) + self.ratios_animo_mundial[i] * a

El procesado de las emociones se realizaría por un lado y la muestra de los resultados por otro. Para la parte de resultados utilicé una placa de prototipado Open Source, Arduino.
El circuito montado sobre la placa es muy simple, es el mismo que sirve para encender y apagar un led, en este caso con las modificaciones oportunas de software para adaptarlo a la medida de las emociones.

Después de compilar Arduino desde sus fuentes sobre un Ubuntu Lucid 64 bits, me puse manos a la obra con el código que se encargaría de recoger los resultados de procesado Python para mostrarlos con el led RGB.
El led mostraría en tiempo real las reacciones de las emociones de la gente siguiendo el siguiente patrón de colores:

enum COLORID {
ROSA = 0,
AMARILLO,
NARANJA,
ROJO,
VERDE,
AZUL,
BLANCO,
NUM_COLORS,
};

Amor rosa, Alegría amarillo, Naranja sorpresa, Rojo ira, Verde envidia, Azul tristeza y Blanco miedo. Modificando suavemente los cambios de colores mediante una función de fading.

Necesitaba mostrar de alguna manera visual cuándo se generaba algún evento importante y para ello introduje una función de flash, de manera que cuando algo extraordinario ocurre el código Python le pasa via serie a Arduino un número completamente diferente a una emoción, le pasa un 9 (las emociones se identifican de 0 a 6), y entonces Arduino se encarga de realizar el apagado y encendido del led mostrando el color de la emoción que ha generado la alerta. Para ello utilizo las siguientes líneas:

for (int numflashes = 5; numflashes >= 0; numflashes-=1){
analogWrite(RED_LED_PIN,0);
analogWrite(GREEN_LED_PIN,0);
analogWrite(BLUE_LED_PIN,0);
delay(1000);
analogWrite(RED_LED_PIN,Colors[lastcolorID].r);
analogWrite(GREEN_LED_PIN,Colors[lastcolorID].g);
analogWrite(BLUE_LED_PIN,Colors[lastcolorID].b);
delay(1000);
}

Antes comentaba que Python le pasaba vía serie las alertas a Arduino, en efecto, vía USB, siendo este usb el cable de alimentación y datos para Arduino. Por ahora es necesario tener una ventana de consola abierta para la interfaz serie que usemos, utilizando ino (cli de arduino) podríamos abrir una sesión de screen ejecutando: ion -p /dev/ttyACM1 por ejemplo.

La portabilidad de nuestra lámpara de emociones llega con la Fonera o un router Acton, mediante el flasheado de OpenWrt podemos dar una nueva vida a la Fonera si es que ya no la utilizamos. La fonera tiene una memoria limitada, pero nos permite instalar python-mini mediante el gestor de paquetes opkg y con unas pinceladas al código y añadiendo una pilas a modo de batería, ya estaríamos preparados para ir a la calle parando a las personas y explicándoles que has conseguido almacenar en una lámpara todo el ánimo mundial.

O bien, como también explican otros, utilizarla en tu mesita de noche para dormirte cuando el nivel de Ira haya disminuido. O quien sabe, igual hacerte rico comprando y vendiendo acciones teniendo en cuenta el ánimo del mundo.

Por el momento yo la utilizaré un tiempo para registrar los grandes cambios de ánimo y temperamento, mediante el envío de un email cuando se produzcan los picos indicados anteriormente. Y lo mismo retroalimentaré mi cuenta de twitter, con las emociones recogidas en un intento de dar conciencia al mundo de si mismo.

Por supuesto me encanta tener la lámpara cerca del ordenador mientras trabajo o escucho música y quedarme pensando cuando esa pequeña luz cambia de color por las emociones de otros, no sé, es una extraña sensación.





Código fuente del proyecto:
Referencias:
Documentación Arduino: http://www.arduino.cc/en/Main/Docs
Gnuplot: http://www.gnuplot.info/

Actualizaciones:
Dic. 2013 - Añadido soporte para la API 1.1 de twitter search

19 comments:

Iván said...

Brutal, magnífico, estupendo,... me quedo sin palabras.

Muy buena idea, mejor solución y fantástica unión del sw con el hw a través de arduino. Lo dicho, para quitarse el sombrero.

Saludos, Iván.

Anonymous said...

Paténtalo si no los has hecho ya.

Dani said...

la idea no es mía, ya hay varios estudios relativos al tema, y alguna que otra lámpara mide sentimientos. Además soy más amigo de las licencias abiertas que de las cerradas. Gracias!

Pendrag said...

Bueno, pues ya sabes el siguiente paso, poder hacer un mapa del mundo que se ilumine en función de los sentimientos en cada zona.

Si te animas, cuenta conmigo ;-)

Luis Antón said...

Increíble. Me parece sencillamente increíble. ¿Qué tal un widget para la web, alojado en Google App Engine tal vez? O un widget para escritorio (Mac, Linux...)

Fantastico, en serio.

Fco. Javier Melero said...
This comment has been removed by the author.
estrategiasysocialmedia said...

De lo mejorcito que he leído últimamente. Enhorabuena!!

Diego Gutierrez said...

Muy bueno. Celebro que exista gente que en lugar de perder el tiempo mirando tele o viendo la vida de otros en facebook, se dedique a cosas como estas.
Despues de leer me quede con una duda: ¿sabes que porcentaje de tweets no contienen ningun tipo de referencia a estados de animo? porque me pregunto que porcentaje del universo representa este segmento que estas midiendo...mera curiosidad.
Abrazo

Dani said...

@Pendrag, efectivamente está en la lista para mejorarlo, tu mismo si te apetece mirarlo el repositorio está para eso. code.google.com/p/animo-del-mundo

@Luis Antón, muy chulo como widget también. Hay mil aplicaciones, una vez tienes los sentimientos es cuestión de pasar los datos a algo. Sonidos, dibujos, movimiento...


@estrategiasysocialmedia gracias!

@Diego Gutierrez, muy interesante, no he mirado si Twitter acepta NOT como query or NOT IN o algo así. Lo apunto en el todo list del repositorio.

Leticia said...

Me encanta tu experimento :-D :-d

leticia said...

Me encanta tu experimentooo :-D Y me imagino que será impresionante cuando esa luz cambie de color :-D

nunes said...

Muy bueno. ¿Has pensado conectar el arduino por ethernet/wifi y comunicarlo con algún servidor? Tengo algo parecido para temperatura, es relativamente sencillo y consigues un sistema casi autónomo (no tiene que estar conectado a un ordenador).

Victor said...

XD genial

Dani said...

@nunes , si, la parte móvil viene de conectar arduino via serie con un hardware de bajo coste como puede ser un router Acton o Fonera 2100, yo lo he hecho con el flasheado de Openwrt en un Acton, e instalando python-mini, cuando lo intenté tuve algún problema con la librería json de python, pero seguro que maquillando un poco el código se puede terminar de arreglar. Hay alguna placa wifi para Arduino pero son un poco más caras, aunque es hw/soft libre y eso te da muchas alternativas.

Octavio said...

genio

Hpsaturn said...

Buena implementacion, de SW y HW, ademas apoyo el SL y proyectos como Arduino, PERO hay q aclarar un punto bastante importante q es la brecha digital mundial, es decir mas o menos el 50% de la poblacion mundial no ha oido un tono telefonico en toda su vida, entonces recomiendo tener cuidado con la magnitud de las palabras q se usan en este post, la intencion es buena pero no refleja la realidad. Un saludo.

Dani said...

He añadido estadísticas en tiempo real http://goo.gl/8pAc0 para que los resultados sean accesibles para tod@s

trucha said...

me encanta..

Anonymous said...

Más que loable y muy refrescante esta búsqueda de respuestas, este inquérito sobre nosotros mismos. Como alguno señala acertadamente no conviene desestimar el argumento de que en prinicipio más del 50% de la población mundial no está representada, e inlcuso la parte que lo esté, es la más manipulada y "dormida" del mundo mundial. Tener cuidado con las emociones/estados que se introducen como premisa; es una lástima no introducir factores "activos" como solidaridad, justicia, compromiso, libertad, paz, servicio, sacrificio, si bien seguramente sobre eso no encontrariáis apenas nada en las redes....porque claro, salud, dinero, amor, o bien bienestar, trinque y sexo (o sea calidad de vida) y mentiras TV (la 5, la 6 y tal), todos l@s borreg@s las aplauden en cualquier sitio...Huid de la ampulosidad de ese título que habéis elegido, pero ánimo y enhorabuena en todo caso

 

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.