El fin de semana del 17 y 18 de Abril de 2010 tuvo lugar una de las primeras iniciativas españolas para promover el Open Government en España, el desafío Abredatos 2010.
Tuve el placer de participar en este desafío junto a mis buenos amigos Javi Santana, Edu Lanchares y Félix López. Como buenos hijos de la diáspora castellano-leonesa por el país intentando buscarse la vida (por lo menos en mi caso) nos apetecía hacer algo cercano a nuestra tierra, así fue como decidimos trabajar con los datos ofrecidos las cortes de Castilla y León.
El problema de estos datos, como el de otros muchos datos públicos, es que la posibilidad de que alguien tenga la intención de reutilizarlos (esa visión de los sistemas de la administración como una plataforma abierta) es, en el mejor de los casos, remota.
De esta manera, nos encontramos con una buena cantidad de datos, no estructurados o semiestructurados, situados en un remoto sitio web y con un formato propietario, más por obligación legislativa, que por una verdadera intención de servicio público.
Procesar estos datos y transformarlos en información es un problema en absoluto trivial.
No existen soluciones algorítmicas que funcionen. La forma más frecuente de afrontar esta dificultad entre los equipos del desafío, así como en los últimos años a raíz de la explosión de la web, consiste en trasladar el problema de las máquinas a las personas, transformando un problema clásico de inteligencia artificial, el procesamiento de texto en lenguaje natural, en un problema de inteligencia colectiva: que sea la multitud de usuarios que visitan esa información los que la filtren.
Nosotros sin embargo, con un claro gusto por lo retro (que podéis esperar de un amante de Lisp), decidimos darle una nueva oportunidad a las técnicas clásicas de inteligencia artificial. El desprestigio de la inteligencia artificial después del conocido como AI Winter es tal, que ahora mismo, nadie que se dedique a la AI puede decirlo si quiere conseguir financiación, así que se utilizan todo tipo de eufemismos para las diferentes parcelas del campo: web semántica, minería de datos, machine learning o procesamiento del lenguaje natural (NLP) que es el que nos interesa.
La idea era utilzar algún tipo de técnica que, dado un volumen de datos textuales fuese capaz de extraer el significado de esos datos y mostrarlos con alguna visualización atractiva en la web.
Afortunadamente, numerosos hombres con barba se han enfrentado a este problema desde los 60 y existen herramientas que nos permiten hacer cosas asombrosas con el texto sin mancharse las manos con matemáticas. Entre ellas está la herramienta que elegimos: GATE un proyecto Java de código abierto que permite construir de forma sencilla sistemas para procesar lenguaje natural. (El proyecto UIMA de Apache también es interesante).
GATE se compone de diferentes componentes que se deben conectar unos con otros en un flujo y que van realizando transformaciones en el texto de entrada.
Nuestro flujo se componía de los siguientes componentes:
- SentenceSplitter: un componente que dado texto de entrada, es capaz de descomponerlo en frases
- DefaultTokeniser: un componente que es capaz de dividir las frases en palabras, teniendo en cuenta guiones y otros accidentes léxicos
- Gazetter: un componente que dado un diccionario de tokens y anotaciones anota todos los tokens producidos por el tokeniser. Utilizamos este componente para marcar palabras sin significado semantico, conocidas como stop words en la jerga del gremio, por ejemplo, preoposiciones, artículos etc.
- TreeTagger: un componente que es capaz de realizar el análisis gramátical de los token, anotándolos con su categoría: verbos, sustantivos, adjetivos, artículos, etc.
- Stemmer: el último componente que dado tokens anotados por el tagger, es capaz de reducir la palabra a su raíz o lexema y forma base o lema. Por ejemplo, para la palabra amaría, la raíz sería am, y la forma base, el infinitvo amar
Al final de este flujo, hemos transformado un montón de texto opaco en una secuencia de palabras con información gramatical. WIN.
El siguiente paso fue agrupar estas palabras con su frecuencia de aparición para pasar a deducir su relevancia. Con un poquito de código Java llevamos a cabo esta labor, aunque tomando algunas decisiones importantes:
- Sólo tendríamos en cuenta sustantivos, adjetivos y verbos que no fuesen stop words
- No contaríamos ocurrencias por su aparición literal, sino que contaríamos como éxito dos palabras con el mismo stem y la misma categoría gramatical.
La siguiente dificultad fue de carácter más práctico, cómo procesar todo el volumen de información a través del sistema construido. Teniendo en cuenta nuestra asombrosa capacidad de cómputo (1 portatil) tuvimos que descartar las herramientas más idóneas y arreglarnoslas poniendo el sistema de procesado en un tomcat con un sencillo servicio web, un script ruby para hacer el scrapping y MongoDB como almacén temporal de datos. Tras conseguir que Java no se comiese toda la memoria disponible por fugas de todo tipo, tuvimos un sistema capaz de procesar una sesión de las cortes en aproximadamente 20 minutos. No es el mejor código que hemos escrito en nuestra vida, pero sirvió para bordear del problema.
Una nueva dificultad apareció debido a las numerosas transformaciones de formato a las que sometimos a los datos: HTML, YAML, documento MongoDB, JSON, tabla SQL, objeto Ruby, pero el verdadero problema surgió cuando visualizamos finalmente los resultados y encontramos que las palabras mas frecuentes eran... totalmente irrelevantes.
Rápidamente nos dimos cuenta, que la relevancia de una palabra desaparecía si por ejemplo se encontraba en todos los documentos indexados (e.g. gracias) o si aparecía con frecuencia muy baja. Con esta idea construimos un algoritmo con dos criterios:
- si una palabra aprecía, en más de N documentos era marcada como irrelevante
- si una palabra aperecía con una frecuencia menor de P era marcada como irrelevante
- El resto de palabras eran marcadas como relevantes.
Jugando con los dos parámetros ajustamos las palabras que se visualizaron finalmente.
Si queréis comprobar la diferencia entre aplicar el algoritmo o no, podéis consumir la API de cortesabiertas.org y comprobar la diferencia entre pedir todas las palabras del corpus:
curl http://cortesabiertas.org/api/words
O pedir las palabras filtradas:
curl http://cortesabiertas.org/api/words?relevant=true
El resultado ya lo conocéis. No es una maravilla, pero con los recursos que teníamos y nuestras carencias estamos bastante contentos. Hay muchas ideas que no nos dio tiempo a implementar, la más interesantes surgen del hecho de que dado el corpus global de palabras, y un objeto: sesión, intervención o procurador, se puede asignar un vector binario o numérico a ese objeto, en función de si dijo esas palabra o cuantas veces la dijo. Esto abre la puerta a utilizar numerosas técnicas de minería de datos sobre los objetos, clasificación o clusterización. Por ejemplo, clusterizar los procuradores por su léxico y ver si coinciden con sus agrupaciones políticas.
En cualquier caso, creemos que sería una herramienta interesante para este tipo de proyectos, y nos gustaría arreglar un poco el código y empaquetarlo de forma que fuese fácilmente usable para otras ideas. Mientras tanto podéis echarle un vistazo al código en github (¡mejoradlo por favo!r)

