Saltar al contenido

1

El segundo elemento que tengo pensado utilizar en mi nuevo proyecto se trata de sbn una librería ruby que permite trabajar con redes bayesianas. Aunque la inferencia está limitada a un algoritmo de resolución aproximado, el Markov Chain Monte Carlo, por lo poco que he probado, esta librería es suficiente para mi proyecto, aunque me gustaría que tuviese algún método de inferencia exacto para poder comparar.

Voy a poner un código sencillo y de paso comento algún detalle de este proyecto sin nombre. Me he decidido a implementar un "adivino del futuro" que funciona de forma aleatoria. La idea es generar un mensaje de texto aleatorio que contiene una serie de variables aleatorias: suerte, amor, salud, riqueza. Hasta ahora he construido una red bayesiana que determina la relación entre estas variables y otras derivadas, para que el mensaje sea coherente (aunque sea aleatorio). Por ejemplo, en el caso del trabajo, si eres afortunado en la riqueza, es posible que que también tengas suerte en el trabajo, pero entonces puede que tu familia te eche de menos y no seas tan afortunado en el amor.

Por ahora he construido una red bayesiana que determina la relación entre una serie de parámetros. La red estará generada "a mano" para que los mensajes que se generen sean divertidos y más o menos coherentes. La descripción de los valores que maneja la red es esta:

  • suerte: es el factor principal de la red, el resto de variables dependen directa o indirectamente de la suerte.
  • amor: todos sabemos lo que es el amor. Por ahora el único hijo del amor es la familia, que depende directamente de nuestra suerte en el amor.
  • salud: fácil de saber qué significa. La salud determina nuestro trabajo, si no tenemos buena salud no podemos trabajar y viceversa.
  • riqueza: determina cúanto dinero y posesiones tenemos. Una parte de la riqueza depende del trabajo y otra del dinero (cómo algo genérico). He supuesto que si te va bien en la riqueza, lo más probable es que te vaya bien en el trabajo o en el dinero (juegos de azar, bolsa, etc) pero es improbable que te vayan bien o mal las dos cosas a la vez (aunque no es imposible).
  • trabajo: depende de la riqueza y de la salud.
  • familia: la más compleja de las variables de este ejemplo, depende del amor y del trabajo, que a su vez depende de la riqueza y de la salud. La idea es que si tenemos mucho trabajo, nuestra familia se resentirá, aunque le vaya bien a nuestra riqueza.

Se trata de un ejemplo muy sencillo, en el que hay que tener en cuenta que no hablo de causalidad, sino de correlación estadística, pero al fín y al cabo es lo que cuenta para "predecir el futuro" y para darle coherencia al mensaje. Una vez generado el mensaje de "suerte", el siguiente paso es vestirlo con palabras que expresen correlación o causalidad indistitamente. Éste es básicamente el truco que usan los adivinos: te va bien en el trabajo, entonces tu familia no estará muy contenta, veo tu futuro muy negro, no vas a tener suerte, pero en el amor te va a ir bien, porque tu familia te va a apoyar. Lo complicado será generar estas palabras que expresen la relación causal, espero que wordnet me sirva de ayuda.

El código ruby es muy sencillo y prácticamente se trata del ejemplo de uso de sbn adaptado a este caso:

require 'rubygems'
require 'sbn'

#puts "Starting: #{Time.now}"

net = Sbn::Net.new("Predicting your future")

#main variable
luck = Sbn::Variable.new(net, :luck, [0.5, 0.5])

#base variables
love = Sbn::Variable.new(net, :love)
luck.add_child(love)
love.set_probability(0.55, {:love => :true, :luck => :true})
love.set_probability(0.45, {:love => :false, :luck => :true})
love.set_probability(0.45, {:love => :true, :luck => :false})
love.set_probability(0.55, {:love => :false, :luck => :false})

health = Sbn::Variable.new(net, :health)
luck.add_child(health)
health.set_probability(0.55, {:health => :true, :luck => :true})
health.set_probability(0.45, {:health => :false, :luck => :true})
health.set_probability(0.45, {:health => :true, :luck => :false})
health.set_probability(0.55, {:health => :false, :luck => :false})

wealth = Sbn::Variable.new(net, :wealth)
luck.add_child(wealth)
wealth.set_probability(0.55, {:wealth => :true, :luck => :true})
wealth.set_probability(0.45, {:wealth => :false, :luck => :true})
wealth.set_probability(0.45, {:wealth => :true, :luck => :false})
wealth.set_probability(0.55, {:wealth => :false, :luck => :false})

#derived variables

work = Sbn::Variable.new(net, :work)
wealth.add_child(work)
health.add_child(work)

work.set_probability(0.8, {:work => :true, :wealth => :true, :health => :true})
work.set_probability(0.2, {:work => :false, :wealth => :true, :health => :true})
work.set_probability(0.2, {:work => :true, :wealth => :true, :health => :false})
work.set_probability(0.8, {:work => :false, :wealth => :true, :health => :false})
work.set_probability(0.5, {:work => :true, :wealth => :false, :health => :true})
work.set_probability(0.5, {:work => :false, :wealth => :false, :health => :true})
work.set_probability(0.1, {:work => :true, :wealth => :false, :health => :false})
work.set_probability(0.9, {:work => :false, :wealth => :false, :health => :false})

money = Sbn::Variable.new(net, :money)
wealth.add_child(money)

money.set_probability(0.6, {:money => :true, :wealth => :true})
money.set_probability(0.4, {:money => :false, :wealth => :true})
money.set_probability(0.2, {:money => :true, :wealth => :false})
money.set_probability(0.8, {:money => :false, :wealth => :false})

family = Sbn::Variable.new(net, :family)
love.add_child(family)
work.add_child(family)

family.set_probability(0.8, {:family => :true, :love => :true, :work => :true})
family.set_probability(0.2, {:family => :false, :love => :true, :work => :true})
family.set_probability(0.6, {:family => :true, :love => :true, :work => :false})
family.set_probability(0.4, {:family => :false, :love => :true, :work => :false})
family.set_probability(0.3, {:family => :true, :love => :false, :work => :true})
family.set_probability(0.7, {:family => :false, :love => :false, :work => :true})
family.set_probability(0.1, {:family => :true, :love => :false, :work => :false})
family.set_probability(0.9, {:family => :false, :love => :false, :work => :false})

#generate evidence
luck_value = rand(2) == 1  ? "true".to_sym : "false".to_sym

evidence = {:luck => luck_value}

net.set_evidence(evidence)

#lookup derived variables, and calculate a value

love_value = rand(0) < net.query_variable(:love)[:true] ? true : false
health_value = rand(0) < net.query_variable(:health)[:true] ? true : false
wealth_value = rand(0) < net.query_variable(:wealth)[:true] ? true : false
work_value = rand(0) < net.query_variable(:work)[:true] ? true : false
money_value = rand(0) < net.query_variable(:money)[:true] ? true : false
family_value = rand(0) < net.query_variable(:family)[:true] ? true : false

puts "luck: #{luck_value} [love: #{love_value} " + \
"(family: #{family_value}), health: #{health_value}, " + \
"wealth: #{wealth_value} (work: #{work_value}, money: #{money_value})]"

#puts "end: #{Time.now}"


Para la parte linguistica del proyecto de generación automática de lenguaje, he encontrado dos paquetes interesantes en ruby: ruby linguistics y ruby wordnet.

Ruby linguistics comprende una serie de utilidades para manejar palabras de forma "linguistica": permite cosas como estas:

  • pluralizar una palabra: "box".en.plural # => "boxes"
  • añadir artículo indefinido: "book".en.a # => "a book"
  • generar el gerundio: "runs".en.present_participle # => "running"
  • calcular ordinales de números: 5.en.ordinal # => "5th"
  • escribir un número en letra: 5.en.numwords # => "five"
  • cuantificar objetos: "cow".en.quantify(5) # => "several cows" || "cow".en.quantify( 1005 ) # => "thousands of cows"
  • infinitivos: "leaving".en.infinitive # => "leave"
  • generar conjunciones:

animals = %w{dog cow ox chicken goose goat cow dog rooster llama pig goat dog cat cat dog cow goat goose goose ox alpaca}
puts "The farm has: " + animals.en.conjunction
Produce:
The farm has: four dogs, three cows, three geese, three goats,two oxen, two cats, a chicken, a rooster, a llama, a pig, and an alpaca

Los ejemplos están tomados de la página del proyecto dónde hay más ejemplos.

Ruby wordnet es un interfaz ruby a Wordnet, una base de datos de palabras organizada de forma semántica. En esta base de datos las palabras se agrupan en synsets (conjuntos de palabras sinónimas) que se relacionan entre ellas con las siguientes relaciones:

  • Hiperonimia e hiponimia: una palabra es hipónima de otra si posée los rasgos semánticos de otra más general.
  • Holonimia y meronimia: si una palabra es parte de otra (ventana es una parte de una casa).
  • Términos coordinados: dos palabras son términos coordinados si comparten un hiperónimo.

He hecho una prueba simple con linguistics y wordnet que pongo aquí cómo ejemplo. En este caso se busca el synset de unas palabras ("happiness", "young" y "man") y se eligen aleatoriamente las palabras de cada synset para formar el mensaje resultado:

require 'rubygems'
require 'linguistics'
require 'wordnet'

Linguistics::use( :en )

puts "Your future"

happiness_synonyms = "happiness".en.synset(1,:noun).synonyms
happiness_word = happiness_synonyms[rand(happiness_synonyms.length)]

young_synonyms = "young".en.synset(1,:adjective).synonyms
young_word = young_synonyms[rand(young_synonyms.length)]

man_synonyms = "man".en.synset(1,:noun).synonyms
man_word = man_synonyms[rand(man_synonyms.length)]

message = "I wish you #{happiness_word}, #{young_word} #{man_word}."

puts message

Una prueba muy sencilla, pero que para poder ejecutarlo hay que resolver un par de problemillas:

  • Para instalar wordnet para ruby (gem install wordnet) hay que tener instalado previamente wordnet (en ubuntu, es simplemente sudo apt-get install wordnet) y el binding de berkeley db para ruby (en ubuntu, sudo apt-get install libdb-ruby1.8). Después de instalar, hay que migrar la base de datos wordnet a berkeley db, con un script incluido en ruby wordnet (./convertdb.rb)
  • Ruby linguistics, al menos en la versión que tengo instalada, la 1.0.5 (gem install Linguistics) está anticuado. Hay que sustituir la función lookupSynsets por lookup_synsets en el fichero wordnet.rb.

La ejecución de este programilla genera frases cómo esta:

I wish you felicity, offspring man.

I wish you happiness, young man.

I wish you happiness, offspring adult male.

Todavía muy simple para lo que quiero conseguir, pero parece que estas librerías pueden ser útiles.

8

Llevo unos días trabajando en una idea que tenía en mente desde que estudié en la universidad. En su momento estuve trasteando con algunos chatbots, que desde el principio me parecieron mucho más realistas que cualquier otro intento de generación de lenguaje natural, a pesar de que se basaban en la simple recombinación aleatoria de palabras. Actualmente hay alguno en funcionamiento, creo que el más famoso es Anna de ikea.

Sin embargo mi ideas es explorar una idea que no sé si está demostrada científicamente: por mucho que lo intentemos, es difícil expresar una misma idea con las mismas palabras, rara vez coincidimos en la mismas mismas palabras y expresiones. El objetivo es hacer un programa que modele este comportamiento y que partiendo de una idea o mensaje genere un texto diferente cada vez. Si la cosa funciona bien, el sistema sería capaz de generar ideas diferentes y de expresarlas cada vez con una combinación de palabras distinta.

Hay un par de asignaturas del posgrado de inteligencia artificial relacionadas con este experimento, "Procesamiento del lenguaje natural" y "Métodos probabilísticos". Así que intentaré usar herramientas de estas dos asignaturas: analizadores sintácticos-semánticos y redes bayesianas, aunque no estoy del todo seguro de que sea lo más apropiado.

Para facilitarme la vida, no voy a hacer un chatbot, sino que intentaré generar un mensaje unidireccional, sin conversación. Una aplicación muy tonta es la de predecir el futuro (un generador de horóscopos) ó la generación artística de textos (¿textos para inspiración?). Si funciona lo suficientemente bien podría usarse en otros contextos dónde se pueda extraer conocimiento (por ejemplo, a través de minería de datos) y generar mensaje aleatorio. En cualquier caso por ahora no es más que un juguete experimental, del que ya tengo algunas pruebas que iré comentando.