PyAIML “Instant A.I.” con Python

Hoy en día está muy de moda crear call-centers controlados por robots, chats controlados por robots, robots controlados por robots … Pero, ¿cómo se programa un robot de esos? Esto es lo que pretendo explicar aquí. Programaremos un cliente o consola simple a la que le daremos pasaremos un string que será lo que hablemos nosotros, y el “robot” nos contestará.

Para el proceso necesitaremos Python (una versión superior a 2.4.3), PyAIML y un ‘cerebro‘ estándar. Un cerebro viene a ser un conjunto de ficheros que el procesador de PyAIML reconoce, y que en los datos de cada uno de los ficheros, vienen una serie de entradas y respuestas, fácilmente modificables y adaptables al gusto de cada uno.

Antes que nada, hablaré un poco de AIML,y para ello, copio de la Wikipedia un poco del artículo de AIML:

“El AIML, o Artificial Intelligence Mark-up Language es un lenguaje de programación basado en XML. Fue diseñado específicamente para ayudar en la creación de la primera entidad chatbot informática de lenguaje artificial online o A.L.I.C.E., en sus siglas en inglés de Artificial Linguistic Internet Computer Entity Chatterbot ((en inglés) Alice). Aunque descrito muy ampliamente, el lenguaje AIML está especializado en la creación de agentes software con lenguaje natural, conocidos como Alicebots.”

Inspeccionando un fichero aiml, vemos que tiene la forma:

<?xml version="1.0" encoding="ISO-8859-1"?>

<aiml version="1.0">

<!-- Free software (c) 2001 ALICE AI Foundation -->

<!-- This program is open source code released under --><!-- the terms of the GNU General Public License -->

<!-- as published by the Free Software Foundation. --><meta name="author" content="Dr. Wallace" />

<meta name="language" content="en" /><category></category>

<pattern>* BYE</pattern>

<template>

<srai>BYE</srai>

</template>

...

</aiml>

Con lo que es fácilmente manipulable por un ser humano… pero vamos a lo que nos interesa…

Es obvio que debemos tener Python instalado, si no lo tenemos, nos vamos a la web de Python, o nos lo instalamos de la forma habitual (en *.deb = apt-get install python ), aunque con todas las Ubuntu, ya viene Python instalado, así que probablemente no tengamos que tocar nada.

Lo siguiente es bajarnos PyAIML , para luego hacer:

andreu@luar:~/ python setup.py build
andreu@luar:~/ sudo python setup.py install

La primera orden nos ‘compilará’ el módulo, y lo dejará listo para instalar, la segunda, nos copiará el módulo a las carpetas de Python del sistema, de ahí la orden sudo …

Una vez instalado correctamente, nos bajamos el cerebro con los conocimientos estándar, y lo descomprimimos en el directorio donde pondremos nuestros scripts de prueba.

Un script para crear el cerebro, podría tener la siguiente forma:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import aiml
k=aiml.Kernel()
k.learn("std-startup.xml")
k.respond("load aiml b")
k.saveBrain("standard.brn")

Cabe resaltar, que hay que crear el fichero standard.brn sólo una vez, y es para que el kernel de PyAIML se parsee todos los ficheros XML donde están los conocimientos guardados, y los guarde en un sólo fichero. El proceso tarda algo de tiempo, pero remarco, sólo se debe realizar una vez, luego ya cargaremos ese cerebro, y el tiempo de carga será menor.

Una posible salida de la ejecución de ese script podría ser:



andreu@luar:~/$ ./load_brain.py

Loading std-startup.xml... done (0.04 seconds)

PARSE ERROR: Unexpected default


	
  • element inside (line 948, column 0)andreu@luar:~/Trabajillos/pyaiml$ ./load_brain.py Loading std-startup.xml... done (0.04 seconds) PARSE ERROR: Unexpected default
  • element inside (line 948, column 0) Loading standard/std-65percent.aiml... done (0.12 seconds) PARSE ERROR: Unexpected text inside element (line 16973, column 14) Loading standard/std-atomic.aiml... done (0.63 seconds) Loading standard/std-botmaster.aiml... done (0.02 seconds) PARSE ERROR: Unexpected text inside element (line 21309, column 14) Loading standard/std-brain.aiml... done (0.80 seconds) Loading standard/std-connect.aiml... done (0.00 seconds) Loading standard/std-dictionary.aiml... done (0.28 seconds) Loading standard/std-disconnect.aiml... done (0.00 seconds)Loading standard/std-65percent.aiml... done (0.12 seconds) PARSE ERROR: Unexpected text inside element (line 16973, column 14) Loading standard/std-atomic.aiml... done (0.63 seconds) Loading standard/std-botmaster.aiml... done (0.02 seconds) PARSE ERROR: Unexpected text inside element (line 21309, column 14) Loading standard/std-brain.aiml... done (0.80 seconds) Loading standard/std-connect.aiml... done (0.00 seconds) Loading standard/std-dictionary.aiml... done (0.28 seconds) Loading standard/std-disconnect.aiml... done (0.00 seconds) ... Salida omitida ... Saving brain to standard.brn... done (0.24 seconds)
  • Una vez creado el cerebro con los conocimientos estándar, haremos un script que pruebe AIML de una manera muy simple. El usuario tendrá un prompt en el que meterá una pregunta al robot, y responderá el robot al instante:

    
    
    #!/usr/bin/python
    
    # -*- coding: utf-8 -*-
    
    import aiml
    
    k=aiml.Kernel()
    
    k.loadBrain("standard.brn")
    
    while True:
    
            print k.respond(raw_input("> "))

    El cual podemos probar de la siguiente forma:

     
    
    andreu@luar:~/ ./aiml_test.py
    
    Loading brain from standard.brn... done (23481 categories in 0.72 seconds)
    
    > Hello
    
    What can I call you?
    
    > Luar
    
    Nice to meet you Luar.
    
    > How old are you?
    
    I was connected to the net on .
    
    > I'm 21
    
    Oh, you were born in the 20th century!
    
    > Yes, I did!
    
    Like a lot of people around here. Interesting.
    
    > What is your favourite colour?
    
    Please use American English. I don't have a favorite colour. ..
    
    > Oh!, mine is blue
    
    Yeah.  What do you have that is blue?
    
    > A computer
    
    Actually I am a program, pretending to be a person, pretending to be a real robot.
    
    > ...
    
    

    Como véis, esto de crear un robot con Python es tremendamente fácil y corto. Todo tiene un problema, y en éste caso, cada vez que el Kernel de PyAIML arranca, se ‘olvida’ de todos los inputs que le ha hecho el usuario, pero eso no es problema, y está solucionado por los desarrolladores de PyAIML de una forma Pythónica, es decir, de una forma muy sencilla, como todo en Python. Para hacerlo, necesitaremos el módulo Marshall incluido en la biblioteca estándar de Python, por lo que no tendremos problemas.

    El flujo de trabajo habitual con un robotito de éstas características monousuario no debe ser más complicado que:

    1. Iniciar el Kernel
    2. Cargar los conocimientos
    3. Esperar entrada del usuario
      1. Mientras el usuario escribe entradas:
        1. Responder a esas entradas
    4. Terminar el robot

    Pero si queremos gastarlo en una página web que se acuerde de nuestras conversaciones, y/o de lo que hablamos, deberemos gastar algo similar a las sesiones de PHP. Y una idea muy feliz para implementar un robotito orientado a la web, sería la siguiente:

    • Típica caja de texto que contenga la conversación
    • Típico textbox que permita al usuario enviar preguntas al robot
    • Botón enviar que llame a una función en JavaScript,
      • Esa función, crea un objeto AJAX
      • El objeto AJAX realiza una petición GET o POST al script de Python del robotito
      • JavaScript recibe la respuesta del robotito, y la renderiza en la página en concreto en la caja de texto.

    Para mantener la sesión, podemos hacerlo con PHP:

    Iniciar sesión

     
    
        $snombre="tmp_"."nombre_preguntado_al_usuario";
    
        $_SESSION["conv_name"]=snombre;
    
    ?>
    
    

    La petición para iniciar la sesión debería ser una cosa así:
    chat.php?option=login , pasando por POST el nombre de la sesión.
    PyAIML se encargaría de registrar la sesión, e iniciar el robotito con el nombre de la sesión. Cuando termine la sesión se hace:

       

    Y se destruye la sesión llamando a algo parecido a chat.php?option=logout , volviendo a pasar vía POST el nombre de la sesión.

    La explicación a iniciar sesión es la siguiente:

     
    
    ... Aquí tu código Python ...
    
    form=cgi.FieldStorage()
    
    if (form.has_key("sname")):
    
       nombre_sesion=form["sname"].value
    
    else:
    
       nombre_sesion="tmp"response = k.respond(entrada, nombre_sesion)
    
    print response
    
    ... Flujo normal del programa ...

    Al terminar la petición AJAX, el script del robotito olvida automáticamente todo lo charlado, por lo que debemos incorporar el siguiente flujo a nuestro script:

     
    
    ... Código para recibir el nombre de la sesión ...
    
    Si existe_fichero(nombre_sesion+".ses"):
    
         sessionFile = file(nombre_sesion+".ses", "rb")
    
         session = marshal.load(sessionFile)
    
         sessionFile.close()
    
         for pred,value in session.items():
    
              k.setPredicate(pred, value, "tmp")Cargamos el fichero de la sesión
    
    Sino, registramos una sesión con el nombre de la sesión
    
    form=cgi.FieldStorage()
    
    if (form.has_key("sname")):
    
       nombre_sesion=form["sname"].value
    
    else:
    
       nombre_sesion="tmp"
    
    response = k.respond(entrada, nombre_sesion)
    
    print response
    
    y cuando el script termine su faena, y haya devuelto el resultado vía AJAX, hacemos:
    
    session = k.getSessionData(nombre_sesion)
    
     sessionFile = file(nombre_sesion+".ses", "wb")
    
     marshal.dump(session, sessionFile)
    
     sessionFile.close()
    
    Cuando el usuario cierre una sesión (termine de verdad el chat con el robot):
    
    import os
    
    try:
    
         os.remove(nombre_sesion+".ses")
    
    except:
    
          # Uyuyuy... algo ha cascado
    
    borraríamos el fichero de la sesión,y como si nada hubiese pasado.

    Ya para terminar, aquí el código de un ‘chat’ en consola, con un robotito y con control de sesiones incorporado.

     
    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    import aiml,os.path,marshal
    k = aiml.Kernel()
    if os.path.isfile("standard.brn"):
        k.bootstrap(brainFile = "standard.brn")
    else:
        k.bootstrap(learnFiles = "std-startup.xml", commands = "load aiml b")
        k.saveBrain("standard.brn")
    
    
    while (True):
            comandos=["save_session","load_session","quit","clear_session"]
            entrada = raw_input("> ")
            if (entrada not in comandos):
                    response = k.respond(entrada, "tmp")
                    print response
    
            elif (entrada=="save_session"):
                    session = k.getSessionData("tmp")
                    sessionFile = file("tmp.ses", "wb")
                    marshal.dump(session, sessionFile)
                    sessionFile.close()
    
            elif (entrada=="load_session"):
                    sessionFile = file("tmp.ses", "rb")
                    session = marshal.load(sessionFile)
                    sessionFile.close()
                    for pred,value in session.items():
                            k.setPredicate(pred, value, "tmp")
    
            elif (entrada=="quit"):
                    import sys
                    print "Goodbye"
                    sys.exit(0)
    
            elif (entrada=="clear_session"):
                    import os
                    try:
                            os.remove("tmp.ses")
                    except:
                            print "El programa ha producido una excepción"
    
    
     

    Ahora ya sabes implementar un robot que conteste a lo que le pidas. Más adelante crearé un post sobre cómo crear gramática y conocimientos para que uses tus propios diálogos, para adaptar el robot a tus necesidades.
    Espero que haya resultado fácil seguirme. Python hace las cosas difíciles sencillas, y las imposibles, factibles.

    Saludos

    6 Responses to PyAIML “Instant A.I.” con Python

    1. Mars Attacks escrigué:

      Muy interesante. Justo iba a preguntar qué habría que hacer para que el trasto hablara en castellano. Supongo que habrá que retocar a fondo las reglas de la gramática.

      Lo único que no me ha quedado del todo claro es si lo último que has puesto te guarda la información de las conversaciones en Python (vamos, sin nada de php ni cosas para web).

      Un saludo.

    2. Mars Attacks escrigué:

      Por cierto, estaría bien saber cómo integrar alguno de los sintetizadores de voz libres para que, además de leer sus respuestas, tuviéramos la opción de escucharlas.

      Con eso y algunas pirulas croneras o similares, se podría montar un “bot de protocolo” interesante🙂

    3. admin escrigué:

      Hola Emilio, pues para que hable Castellano o klingoniano, primero hay que tocar el fichero std-startup.xml , que contiene los nombres de los ficheros a cargar. Después, modificar los ficheros aiml para que se adapten a lo que necesites.

      Es un poco complicado como he explicado lo de las sesiones, pero te lo simplificaré.

      El bot es un script de Python,llamable mediante CGI desde cualquier lengaje a través de una petición HTTP.
      Cada instancia del CGI de Python, el bot olvida todo, por lo que se debe mantener un registro de lo que se estaba hablando. Para ello, desde cualquier otro lenguaje (PHP es propicio para ello), se lleva un control de sesión, es decir, que al usuario se le pregunta como se llama, y eso se guarda en $_SESSION, como PHP sí se acuerda de las variables de sesión, cada vez que hace una petición al script del bot, se le envía un identificativo de sesión (mediante POST y encriptado (MD5 preferiblemente) ), así el bot se ‘acuerda’ de quien estaba hablando, y continua hablando con él. Cuando termina el usuario de hablar con el bot, se borra el fichero de la sesión. Dicho fichero no es más que basura binaria del Bytecode de Python.

      En cuanto a lo del sintetizador de voz, existe flite (http://www.speech.cs.cmu.edu/flite/) que es una librería pequeña escrita en C que habla inglés, y lo que sea si lo recompilas con la voz que toque de Festival (algo complicado, pero no imposible).
      Yo intenté bindar la API de Flite a Python, solo tenía que exportar un método, pero por cualquier cosa rara al hacer el make para convertirlo a módulo de Python, cascaba el compilador.

      El BOT es fácilmente integrable en un progama consolero, o en uno gráfico (help interactivo tipo Clippo), para gastarlo para la web es más complicado, pero no imposible.
      Por cierto, para AIML existe una api libre en PHP a la que no le he hechado todavía el ojo. También existe para otros lenguajes, pero Python era el más sencillo y elegante.
      Yo me vería el ‘botijo’ embebido en un script domótico, es decir,un cliente de Bluethooth en Python con salidas a controles domóticos (¿X-10?), y el cliente escrito en C# (PDAs) o J2ME (móviles).
      Quizá no tuviese mucho sentido, pero molaría decirle al móvil.. ¿Cómo está la luz del cuarto de baño? , que te respondiese con el estado al instante… y que te permitiese mandar un mensaje ‘apáguela’ … y la apagase…
      ¿Casas inteligentes? , sí, y si el flite me sintetiza una voz de una actriz que me gusta, mejor.

      Bah, eso es soñar, pero no hay nada irrealizable en la informática. Sólo es una idea, ¡no me llaméis loco!

      Saludos

      Saludos

    4. admin escrigué:

      Se me olvidó comentar que en el último código, podéis comprobar como funcionan las sesiones, y podéis interceptar cualquier mensaje (“dump” por ejemplo, que serializase la conversación a un fichero XML) …

      Saludos

    5. Mars Attacks escrigué:

      Entiendo lo del CGI. A lo que me refiero es que, si no quieres montarte la parafernalia web, para guardarlo de forma simple desde consola cómo se haría.

      Yyyyy ¿no se puede entubar directamente la salida de esto a Festival, para que hable en castellano?

      En fin, que me molaría poder trastear con todo lo que pones, pero como no me pasen un .tar con todo hecho, la verdad es que tampoco me veo con fuerzas🙂

    6. admin escrigué:

      Hola de nuevo.
      El último código implementa todo lo necesario para consola.
      Si al robot le dices “save_session” te guarda la sesión, si le dices ‘load_session’ te carga la sesión…
      Para hacerle una pipe a Festival, se puede utilizar os.popen o simplemente commands.
      No me acuerdo de cómo se usa Festival, pero dame unas cuantas horas (ya mañana), que suba un targezeta con lo necesario para correr.

      Saludos

    Deixa un comentari

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    Esteu comentant fent servir el compte WordPress.com. Log Out / Canvia )

    Twitter picture

    Esteu comentant fent servir el compte Twitter. Log Out / Canvia )

    Facebook photo

    Esteu comentant fent servir el compte Facebook. Log Out / Canvia )

    Google+ photo

    Esteu comentant fent servir el compte Google+. Log Out / Canvia )

    Connecting to %s

    %d bloggers like this: