jueves, 3 de noviembre de 2016

Red neuronal enfocada en el aprendizaje supervisado del ajedrez

Enfocado en el reciente trabajo teórico de Google DeepMind (AlphaGo), he desarrollado e implementado una versión en TensorFlow de dicha arquitectura, pero realizando modificaciones para enfocar el diseño en el juego del ajedrez en lugar del Go (el paper original es: https://storage.googleapis.com/deepmind-media/alphago/AlphaGoNaturePaper.pdf).

El modelo ideado originalmente por DeepMind fue divulgado con detalle en este artículo: https://deepmind.com/research/alphago/, y mi implementation del mismo (con las modificaciones necesarias para enfocarlo todo en el juego del ajedrez), se encuentra en el siguiente repositorio de mi cuenta personal en GitHub: https://github.com/Zeta36/Policy-chess. Se trata como podéis ver, de un desarrollo Python usando el framework de Google, TensorFlow.

Os dejo a continuación toda la información técnica sobre mi trabajo, aunque siento no tener tiempo para traducirlo y simplemente copiaré a continuación la introducción que hice del mismo en inglés (prometo más adelante escribir una entrada dedicada a divulgar de manera más sencilla el potencial de esta red neuronal, y el indicio que puede contener sobre el modo en que nuestro propio cerebro biológico realiza ciertas tareas relacionadas con la generalización de conceptos, en este caso; aprender a clasificar con cierta habilidad y de manera "intuitiva" cual es una buena jugada de ajedrez de una mala):

A Policy Network in Tensorflow to classify chess moves

This work is inspired in the SL policy network used by Google DeepMind in the program AlphaGo (https://storage.googleapis.com/deepmind-media/alphago/AlphaGoNaturePaper.pdf).
The network models the probability for every legal chess move given a chess board based only in the raw state of the game. In this sense, the input s to the policy network is a simple representation of the board state using a tensor (batc_sizex8x8x8) with information of the chess board piece state, the number of the movement in the game, the current player, etc.
The SL policy network Pσ(a|s) alternates between convolutional layers with weights σ, and rectifier nonlinearities. A final softmax layer outputs a probability distribution over all legal moves a (labels).
The policy network is trained on randomly sampled state-action pairs (s, a), using stochastic gradient ascent to maximize the likelihood of the human move a selected in state.

Preparing the Data sets

We train the 3-layer policy network using any set of chess games stored as PGN files. To prepare the training and the validation data set, we just need to download many PGN file (more data means more accuracy after the training) and put them in the datasets folder (there is in the repository some pgn examples to use).
After that, we run in the console:
python pgn-to-txt.py
In this way, the PGN files will be reformated in the proper way, and chuncked in a tuple of (board state, human move). We the pgn-to-txt.py script finish, go into the datasets folder and copy almost all the "*.txt" files generated into a new folders called "data_train", and some text files into another folder called "data_validation".
Finally, you have to run
python pgn-to-label.py
And we will get the labels for the SL. This labels will be generated and saved in a labels.txt file inside the "labels" folder.

Training

Training is a easy step. Just run:
python train.py
You can adjust before if you wish some hyperparameters inside this python script.

Playing

Once the model is trained (and the loss has converged), you can play a chess game against the SL policy network. Just type:
python play.py
The machine moves will be generate by the policy network, and the human moves in the game will be asked to you to be type in the keyboard. In order to move, you have to know the san Algebraic notation(https://en.wikipedia.org/wiki/Algebraic_notation_(chess)).
The game board is printed in ASCII, but you can use any online chess board configuration (like this http://www.apronus.com/chess/wbeditor.php) to mimic the movements so you can see clearly the game.

Requirements

TensorFlow needs to be installed before running the training script. TensorFlow 0.10 and the current master version are supported.
In addition, python-chess must be installed for reading and writing PGN files, and for the play.py script to work.

Results

After some thousands of training steps, the model is able to generalize and play a reasonable chess game based only in the prediction of the human movements in the training process.

domingo, 2 de octubre de 2016

Implementación en TensorFlow de la red neuronal generativa (WaveNet) enfocada en la generación de texto

Enfocado en el reciente trabajo teórico de Google DeepMind (WaveNet), he desarrollado e implementado una versión en TensorFlow de dicha arquitectura, pero realizando modificaciones para enfocar el diseño en la generación automática de textos (el paper original es: https://arxiv.org/pdf/1609.03499.pdf).

El modelo ideado originalmente por DeepMind fue divulgado con detalle en este artículo: https://deepmind.com/blog/wavenet-generative-model-raw-audio/, y mi implementación del mismo (con las modificaciones necesarias para enfocarlo todo en la generación de texto), se encuentra en el siguiente repositorio de mi cuenta personal en GitHub: https://github.com/Zeta36/tensorflow-tex-wavenet. Se trata como podéis ver, de un desarrollo Python usando el framework de Google, TensorFlow.

Os dejo a continuación toda la información técnica sobre mi trabajo, aunque siento no tener tiempo para traducirlo y simplemente copiaré a continuación la introducción que hice del mismo en inglés:

A TensorFlow implementation of DeepMind's WaveNet paper for text generation.


This is a TensorFlow implementation of the WaveNet generative neural network architecture for text generation.

Previous Work

Originally, the WaveNet neural network architecture directly generates a raw audio waveform, showing excellent results in text-to-speech and general audio generation (see the DeepMind blog post and paper for details).
This network models the conditional probability to generate the next sample in the audio waveform, given all previous samples and possibly additional parameters.
After an audio preprocessing step, the input waveform is quantized to a fixed integer range. The integer amplitudes are then one-hot encoded to produce a tensor of shape (num_samples, num_channels).
A convolutional layer that only accesses the current and previous inputs then reduces the channel dimension.
The core of the network is constructed as a stack of causal dilated layers, each of which is a dilated convolution (convolution with holes), which only accesses the current and past audio samples.
The outputs of all layers are combined and extended back to the original number of channels by a series of dense postprocessing layers, followed by a softmax function to transform the outputs into a categorical distribution.
The loss function is the cross-entropy between the output for each timestep and the input at the next timestep.
In this repository, the network implementation can be found in wavenet.py.

New approach


This work is based in one implementation of the original WaveNet model (Wavenet), but applying some modifications.
In summary: we are going to use the WaveNet model as a text generator. We'll use raw text data (characters), instead of raw audio files, and once the network is trained, we'll use the conditional probability found to generate samples (characters) into an self-generating process.

Only printable ASCII characters (Dec. 0 up to 255) is supported right now.

Results


Pretty interesting results are reached!! Feeding the network with enough text and training, the model is able to memorize the probability of the characters disposition (in a lenguage), and generate later even a very similar text!!

For example, using the Penn Tree Bank (PTB) dataset, and only after 15000 steps of training (with low set of parameters setting) this was the self-generated output (the final loss was around 1.1):

"Prediction is: 300-servenns on the divide mushin attore and operations losers nis called him for investment it was with as pursicularly federal and sotheby d. reported firsts truckhe of the guarantees as paining at the available ransions i 'm new york for basicane as a facerement of its a set to the u.s. spected on install death about in the little there have a $ N million or N N bilot in closing is of a trading a congress of society or N cents for policy half feeling the does n't people of general and the crafted ended yesterday still also arjas trading an effectors that a can singaes about N bound who that mestituty was below for which unrecontimer 's have day simple d. frisons already earnings on the annual says had minority four-$ N sance for an advised in reclution by from $ N million morris selpiculations the not year break government these up why thief east down for his hobses weakness as equiped also plan amr. him loss appealle they operation after and the monthly spendings soa $ N million from cansident third-quarter loan was N pressure of new and the intended up he header because in luly of tept. N million crowd up lowers were to passed N while provision according to and canada said the 1980s defense reporters who west scheduled is a volume at broke also and national leader than N years on the sharing N million pro-m was our american piconmentalist profited himses but the measures from N in N N of social only announcistoner corp. say to average u.j. dey said he crew is vice phick-bar creating the drives will shares of customer with welm reporters involved in the continues after power good operationed retain medhay as the end consumer whitecs of the national inc. closed N million advanc"

This is really wonderful!! We can see that the original WaveNet model has a great capacity to learn and save long codified text information inside its nodes (and not only audio or image information). This "text generator" WaveNet was able to learn how to write English words and phrases just by predicting characters one by one, and sometimes was able even to learn what word to use based on context.

This output is far to be perfect, but It was trained in a only CPU machine (without GPU) using a low set of parameters configuration in just two hours!! I hope somebody with a better computer can explore the potential of this implementation.

If you want to check this results, you just have to type this in a command line terminal (this will use the trained model checkout I uploaded to the respository):
python generate.py --text_out_path=output.txt --samples 2000 
./logdir/train/2016-10-02T10-45-10/model.ckpt-14999

Requirements


TensorFlow needs to be installed before running the training script. TensorFlow 0.10 and the current master version are supported.

Training the network


You can use any text (.txt) file.

In order to train the network, execute
python train.py --data_dir=data

to train the network, where data is a directory containing .txt files. The script will recursively collect all .txt files in the directory.
You can see documentation on each of the training settings by running
python train.py --help

You can find the configuration of the model parameters in wavenet_params.json. These need to stay the same between training and generation.

Generating text


You can use the generate.py script to generate audio using a previously trained model.

Run
python generate.py --samples 16000 model.ckpt-1000
where model.ckpt-1000 needs to be a previously saved model. You can find these in the logdir. The --samples parameter specifies how many characters samples you would like to generate.
The generated waveform can be stored as a .txt file by using the --text_out_path parameter:
python generate.py --text_out_path=mytext.txt --samples 1500 model.ckpt-1000
Passing --save_every in addition to --text_out_path will save the in-progress wav file every n samples.

python generate.py --text_out_path=mytext.txt --save_every 2000 --samples 1500 model.ckpt-1000

Fast generation is enabled by default. It uses the implementation from the Fast Wavenet repository. You can follow the link for an explanation of how it works. This reduces the time needed to generate samples to a few minutes.

To disable fast generation:
python generate.py --samples 1500 model.ckpt-1000 --fast_generation=false

Missing features

Currently, there is no conditioning on extra information.

domingo, 18 de septiembre de 2016

Trasteando con el "Juego de la vida" de John Horton Conway

El Juego de la vida es un diseño de programación ideado por el matemático británico John Horton Conway en 1970, y basa su funcionamiento en un "mundo" en dos dimensiones (2D) el cual contiene cierto número finito de celdas que pueden estar o no ocupadas por una estructura. Posteriormente estas celdas varían su estado gracias a una "física" (conjunto de reglas) que dictan la dinámica y el estado del tablero. Aunque lo más importante y destacable de este proyecto, es el hecho de ver como un diseño tan simple es capaz de conseguir con su dinámica temporal que a veces emerjan interesantes y complejos patrones de movimiento.

Las reglas del juego original son las siguientes:
  1. Una celda puede estar activa (on) o no activa (off). Si está activa se dice que es una célula viva.
  2. Una célula muerta (celda off) con exactamente 3 células vecinas vivas "nace" (al turno siguiente esta celda muerta estará viva).
  3. Una célula viva con 2 ó 3 células vecinas vivas sigue viva, en otro caso muere o permanece muerta (por "soledad" o "superpoblación").
Y esto es todo. Una reglas muy sencillas, que dan lugar a patrones a veces asombrosamente complicados. Esta es de hecho, la clave de todo: comprobar como es posible que surja complejidad a partir de un comportamiento de base muy simple.

A continuación os muestro brevemente lo fácil que es programar esta "física" en Javascript:



var Game = function(board) {

this.board = board;

this.width = board.width;

this.height = board.height;

};


Game.prototype.get = function(x, y) {

return this.board.get(x,y);

};


Game.prototype.set = function(x, y, value) {

this.board.set(x,y, value);

};


Game.prototype.step = function() {

var

current = this.board,

width = current.width,

height = current.height,

i, j, neighbours, cell

;

this.board = new Board(width, height);


for (i = 0; i < width; i++) {

for (j = 0; j < height; j++) {

neighbours = current.neighbours(i,j);

cell = current.get(i,j);

if (neighbours === 3)

this.board.set(i,j, 1);

else if (neighbours === 2)

this.board.set(i,j, cell);

else

this.board.set(i,j, 0);

}

}

};

Apenas 38 líneas de código que nos van a permitir disfrutar de increíbles patrones de movimiento como los siguientes (es importante hacer notar de nuevo que el estado inicial de las celdas va a determinar también el patrón de movimiento al principio):

Pues bien, a petición de la idea de un amigo (Sergio Hernandez), he desarrollado una variación sobre el Juego de la vida, donde simplemente vamos a tener en el "mundo" dos tipos de células vivas (en on): células verdes y células rojas (y también celdas muertas en off). Las reglas serán las mismas para cada tipo de célula viva, las cuales vivirán siguiendo sus propias normas (como si fuesen cepas independientes).

Dos pequeños cambios se añaden no obstante a las reglas orginales:
  1. Si una celda resulta que es ocupada en un paso (step) a la vez por una célula roja y una verde se elige aleatoriamente el tipo de célula viva que permanecerá en esa casilla (dado que una celda sólo puede tener un estado determinado).
  2. Si una célula viva detecta que en el siguiente paso (step) morirá por "soledad" o "superpoblación", cambiará su tipo. De modo que si una célula viva roja detecta que morirá en el siguiente paso, pero que si se vuelve verde no lo hará, cambiará su estado a célula verde.
El resultado de estos cambios es el siguiente (generando al azar el estado inicial del tablero):

Es interesante ver el modo en que estas dos cepas de células vivas compiten de alguna manera por el espacio disponible. A veces da la sensación de que se fagocitan unas a otras, o que incluso intenan "escapar" cuando notan la proximidad de "rivales". En muchas ocaciones incluso una cepa completa es eliminada por la contraria, terminando el tablero exclusivamente con células verdes o rojas.

Nota:  como ya hemos dicho, el resultado dependerá del estado inicial de las celdas, y en este ejemplo dicho estado inicial es aleatorio, por lo tanto, si queréis ver una nueva configuración dinámica simplemente tenéis que refrescar esta página en el navegador (botón F5). También es posible hacer 'click' en cualquier lugar del tablero para generar nuevas celdas vivas aleatoriamente, con lo cual podremos "modificar" el estado dinámico del juego sin necesidad de actualizar la página completa.
También es importante ver como esta modificación parece de algún modo haber estabilizado el comportamiento del sistema, puesto que ahora, de media, la duración del movimiento dinámico antes de caer en estructuras fijas (que no avanzan sino que se quedan quitas con patrones estáticos) parece haber aumentado. Por ejemplo, en el siguiente caso os muestro el comportamiento del nuevo Juego de la vida desarrollado, pero en lugar de comenzar con una configuración al azar, voy a comenzar con una configuración muy parecida a la que usamos antes para el juego original (5 células vivas de cada color dispuesta en similar configuración):

Futuro trabajo.

Creo que es interesante seguir explotando este tipo de desarrollos en un intento de sacar algún tipo de conclusión teórica sobre la base del mismo. En este sentido es seguro que Sergio Hernandez me comentará algunas nuevas ideas de cambio, y yo, por mi parte, tengo intención de aplicar al desarrollo anterior un algoritmo de computación evolutiva buscando en principio aquellas configuraciones que más movimiento producen tras n pasos (es decir, aquellas que más "calor" generan, que más "energía" consumen y por lo tanto que maximizan la "entropía"), con lo cual espero conseguir (descubrir) aquellas configuraciones que den lugar a los patrones más complejos posibles, y detectar también si es cierto que complejidad y entropía (consumo de energía libre) correlacionan necesariamente, y si dicho resultado se puede generalizar para otras "físicas" (quizás incluso la de nuestro propio mundo).

Un saludo.

lunes, 15 de agosto de 2016

Ejemplo de programación de extensiones para Chrome

El aburrimiento es la enfermedad de las personas afortunadas; los desgraciados no se aburren, tienen demasiado que hacer.(A. Dufresnes)

Ayer por la tarde estaba aburrido (muy aburrido), y se me ocurrió para pasar el rato desarrollar una extensión para el navegador Chrome; de manera que al mismo tiempo de quitarme el aburrimiento, aprendía algo nuevo (y quizás útil para el futuro): en concreto, me propuse estudiar el modo en que podría interactuar programáticamente, usando jQuery; sobre una pestaña cualquiera abierta en el navegador.

Como ejemplo; se me ocurrió utilizar la versión web de Whatsapp (https://web.whatsapp.com/) para, desde el navegador, crear y cargar una extensión (plugin) capaz de buscar en una conversación concreta de un contacto mio su última respuesta, y de responderle en consecuencia de manera autónoma usando una API de inteligencia artificial conversacional externa (finalmente me decidí por https://www.cleverbot.com/ porque fue el único que encontré capaz de hablar en castellano).

Finalmente lo conseguí, y el resultado final ha sido el deseado: una extensión capaz de leer el último comentario que un contacto me haya escrito, y responder automáticamente usando el motor de IA de Cleverbot. Aunque, por desgracia, Cleverbot en castellano no está demasiado bien conseguido y a veces se lía con respuestas poco realistas. Vamos, que no va a pasar el test de Turing con vuestros amigos (quizás si tenéis amigos que hablen inglés lo haga más decentemente) :P.

Explicación técnica.

Paso a continuación a dar algunas explicaciones técnicas para que cualquier interesado pueda hacer la prueba él mismo. Los pasos son muy sencillos:

Os bajáis todo el código fuente de la extensión desde aquí, y lo descomprimís todo en una carpeta. El resultado será el siguiente:


En el manifest.json vamos a especificar los datos generales de la extensión: nombre, versión. permisos, etc.


{
    "name": "Chat de Turing",
    "version": "1.5",
    "manifest_version": 5,
    "background": {
        "scripts": ["init.js"]
    },
    "page_action": {
        "default_icon": "icon.png"
    },
    "permissions": ["tabs", "http://*/*", "https://*/*"]
}
manifest.json

El fichero init.js, el cual hará de background del plugin de Chrome, será el encargado de montar el framework jQuery y el fichero principal con el script de contenidos: content.js:


function checkForValidUrl(tabId, changeInfo, tab) {
    chrome.pageAction.show(tabId);
}

chrome.tabs.onUpdated.addListener(checkForValidUrl);

chrome.pageAction.onClicked.addListener(function(tab) {
 chrome.tabs.executeScript(tab.id, { file: "jquery.js" }, function() {
  chrome.tabs.executeScript(tab.id, { file: "content.js" });
 });
});
init.js

Por último, os muestro el fichero de contenidos con el Javascript encargado de leer la última respuesta del contacto y de contestar a la misma usando el motor de inteligencia artificial del que ya hemos hablado:


function getSaludo()
{
 var textArray = [
  "como estas?",
  "que tal estas?",
  "hola que tal?",
  "holaa como estas?",
  "dime algo",
  "dime algo",
  "buenas",
  "hola",
  "que tal?",
  "hola",
 ];
 return textArray[Math.floor(Math.random() * textArray.length)];
}

function randomIntFromInterval(min, max)
{
    return Math.floor(Math.random()*(max-min+1)+min);
}

function dispatch(target, eventType, char) {
 var evt = document.createEvent("TextEvent");    
 evt.initTextEvent (eventType, true, true, window, char, 0, "es-ES");
 target.focus();
 target.dispatchEvent(evt);
}

function triggerMensaje() {
 var event = new MouseEvent('click', {
   'view': window,
   'bubbles': true,
   'cancelable': true
  });
  
 document.querySelector(".icon.btn-icon.icon-send").dispatchEvent(event);
}

function llamarClaverBot(texto)
{
 $.ajax({
        url: "https://xxxxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),
        type: "get",        
        success: function (response) {               
     console.log(response);
     dispatch(document.querySelector("div.input"), "textInput", response);
     triggerMensaje(); 
        },
        error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus, errorThrown);
        }
    }); 
}
 
function actuar()
{
 var p = Math.floor(Math.random() * 100) + 1; 
 var ultimo = $( ".message-in .message-text" ).last().text();
 
 if(ultimo !== "")
 {
  ultimo = ultimo.split("]")[1];
  ultimo = ultimo.split(":")[1];
 }
 
 if(ultimo && ultimo !== "" && ultimo != ultima_respuesta)
 {
  ultima_respuesta = ultimo;
  llamarClaverBot(ultimo);
 }
 else if(!iniciado)
 {   
  dispatch(document.querySelector("div.input"), "textInput", getSaludo());
  triggerMensaje(); 
 }
 
 iniciado = true;   
}

var iniciado = false, ultima_respuesta = "";

$(document).ready(function()
{
 setInterval(actuar, 15000);
});
content.js

Como  podéis ver, el código fuente es muy sencillo, y se trata simplemente de montar un temporizador que cada cierto tiempo (15000 milisegundos), va a ir mirando el DOM de la página web de nuestro Whatsapp, buscando por si nuestro contacto nos ha respondido algo nuevo:

var ultimo = $( ".message-in .message-text" ).last().text();

Y si es ese el caso (ultimo != ultima_respuesta) y el algoritmo encuentra una nueva respuesta, se llama por AJAX a la IA de CleverBot y se captura la respuesta del mismo:

    $.ajax({
        url: "https://xxxxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),
        type: "get",        
        success: function (response) {                 
     console.log(response);
     dispatch(document.querySelector("div.input"), "textInput", response);
     triggerMensaje(); 
        },
        error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus, errorThrown);
        }
    }); 

Finalmente, se despacha esta respuesta modificando programáticamente el DOM de la página simulando las pulsaciones del teclado para escribir la respuesta (usando el evento "TextEvent"), y simulando también una llamada al botón de enviar de la página buscando por jQuery el botón (".icon.btn-icon.icon-send") y forzando un evento 'click' sobre él:

        var event = new MouseEvent('click', {
   'view': window,
   'bubbles': true,
   'cancelable': true
  });
  
 document.querySelector(".icon.btn-icon.icon-send").dispatchEvent(event);

Nota:
Podéis ver que en la función llamarCleverBot() se realiza una llamada AJAX a un servidor externo pasando como parámetro GET la respuesta de nuestro contacto. Este servidor externo debéis montarlo vosotros subiendo la carpeta "bot" que os he mostrado arriba a cualquier servidor web que tengáis por ahí (valen los gratuitos que se ofrecen por Internet, siempre y cuando tenga seguridad SSL; es decir protocolo https).

Modo de uso.

¿Cómo usar esta extensión del navegador? Muy sencillo:

1) Descomprimís el fichero y subís la carpeta "bot" a algún servidor web  con protocolo https activo que tengáis por ahí.

2) En el fichero content.js modificáis la url de la llamada a CleverBot para que apunte a vuestro servidor web. Es decir; que debéis cambiar las xxxxx de esta línea por el dominio de vuestro servidor:

url: "https://xxxxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),


3) Vais a vuestro navegador Chrome, abrís una nueva pestaña, y escribís en la barra de direcciones lo siguiente: chrome://extensions/ y pulsáis el botón del "Entrar".


Activáis el "Modo de desarrollador" y pulsáis en el botón "Cargar extensión descomprimida...", tras lo cual debéis buscar y seleccionar la carpeta donde habéis descomprimido todo el código fuente del fichero.

El resultado final deberá ser similar al de la imagen anterior, donde aparecerá una nueva extensión (comprobad que esté "Habilitada") llamada "Chat de Turing", que es el nombre que le dimos en el manifest.json a nuestra extensión.



4) Creáis una nueva pestaña en el navegador y abrís en ella la web de Whatsapp: https://web.whatsapp.com/ (si no lo habéis hecho antes, deberéis seguir unas pequeñas instrucciones para conectar a vuestro Whatsapp en modo Web):



5) Si habéis seguido bien las instrucciones, os aparecerá arriba a la derecha de la barra de direcciones un icono con el logo de Whatsapp (junto a la estrella para añadir a favoritos). Simplemente debéis pulsar en dicho icono y el script se activará.

6) Por último, entrad en alguna conversación con algún amigo o grupo y saludar. En cuanto te responda, el plugin que hemos montado detectará su respuesta y le replicará usando la IA conversacional de CleverBot.

Resultados.

Pues como ya os he comentado, por desgracia CleverBot no se encuentra en el estado del arte en cuanto a su capacidad para conversar de manera inteligente; pero sí es cierto que a veces tiene respuestas muy ocurrentes, y yo al menos me he hartado de reír en la conversación que ha tenido con un par de amigos míos (y en grupos con mucha cantidad de gente hablando).

Total, ha sido un pequeño divertimento que realicé en unas pocas horas de aburrimiento. Espero que al menos sea útil a alguien. Yo, por ejemplo; he aprendido mucho sobre la programación (y el potencial) de las extensiones Chrome; y en mi profesión todo lo que se aprende tarde o temprano puede llegar a servir para algo ;).

Os dejo para finalizar un pequeño ejemplo de conversación que tuvo el bot con una amiga:



Un saludo.

Edito: 16/08/2016
------------------------------------------------------------------------------------------------------------------------------------

Para probar un poco mejor la capacidad real de Cleverbot para confundir a un humano y hacerle pensar que habla con otra persona, he decidido modificar el código fuente para que el bot tenga una conversación privada con un desconocido cualquiera del chat genérico elchat.com.

El resultado ha sido asombroso. El bot se ha comportado, como era de esperar, mucho mejor con desconocidos que con mis contactos de Whatsapp, que me conocen ya y se huelen enseguida que estoy diciendo cosas raras. Pero como os digo, con desconocidos la cosa cambia: la verdad es que me he hartado de reír. Os voy a pegar una conversación de ejemplo entre un tal LUIS LA7294 y nuestro bot, al que llamé Mary4840. Por momentos es una conversación incluso interesante, como cuando el chico le habla en portugués y el bot es capaz de seguirle la corriente hablando también en portugués. De hecho, pasó tan bien el "test de Turing" en este caso concreto, que tuve que cortar la conversación al cabo de 15 minutos aburrido ya:

Mary4840: Hello
LUIS LA7294: hola
Mary4840: ¿Puedo preguntarte algo?
LUIS LA7294: si claro
Mary4840: Como estas?
LUIS LA7294: bien gracias y tu
Mary4840: Muy Bien
LUIS LA7294: pero me ivas a preguntar algo
LUIS LA7294: que es
Mary4840: Que es que?
LUIS LA7294: pues lo ke me ivas a preguntar
Mary4840: No te iba a preguntar nada
LUIS LA7294: me dijistes puedo preguntarte algo
Mary4840: Si
LUIS LA7294: si si puedes
LUIS LA7294: a ver ke es
Mary4840: Que cosa?
LUIS LA7294: nada olvidalo
LUIS LA7294: de donde te comunicas
Mary4840: Que importa
LUIS LA7294: si si me interesa de dond ete comunicas
Mary4840: Alguna vez probaste carne humana?
LUIS LA7294: quien kite y vivamos cercas
LUIS LA7294: la verda asta ahorita no
Mary4840: Soy mujer
LUIS LA7294: ke acaso tu ya
Mary4840: Si
LUIS LA7294: como hacer el amor
LUIS LA7294: o comertela
Mary4840: Comerme el que?
LUIS LA7294: pues la cerne humanana
Mary4840: Quien gano la final de la copa brasil 2014?
LUIS LA7294: alemania
LUIS LA7294: mary de donde eres
Mary4840: Colombia
LUIS LA7294: ok
LUIS LA7294: y donde vives ahora
Mary4840: Barcelona
LUIS LA7294: ok y ke haces aya
Mary4840: A juego para un equipo de futbol
LUIS LA7294: mandame uan fotomary me gustaria conocerte
LUIS LA7294: que buena onda
Mary4840: Vos sos hombre?
LUIS LA7294: si asi es
Mary4840: De donde eres?
LUIS LA7294: de los angeles ca
LUIS LA7294: mandame uan fot para conicerte
Mary4840: No!!!!
LUIS LA7294: y eso por ke no
Mary4840: Yo tengo un elefante que se llama trompita
LUIS LA7294: oye mary kiero conocerte
Mary4840: Yo no
LUIS LA7294: mandam euan foto
LUIS LA7294: y eso por ke no kieres conocerme
Mary4840: Sí quiero
LUIS LA7294: ok
LUIS LA7294: pues mandame una foto
Mary4840: Como?
LUIS LA7294: pues aki
Mary4840: Que asco, no sabes escribir?
LUIS LA7294: si se por ke dices eso
Mary4840: Por que eres genial
LUIS LA7294: ok
LUIS LA7294: pues mandam euna foto
Mary4840: Es congruente lo que dices
LUIS LA7294: si asi es
Mary4840: Muy bien
LUIS LA7294: mandamelal please
Mary4840: Non so come si fa
LUIS LA7294: sabes portuges
Mary4840: Sim
LUIS LA7294: você pode falar em Português
Mary4840: Estou falando em português
LUIS LA7294: envie-me uma imagem
Mary4840: No puedo
LUIS LA7294: porque não
Mary4840: Porque nao nao é resposta
LUIS LA7294: por favor
Mary4840: Por favor que
LUIS LA7294: envie-me uma imagem
Mary4840: No puedo
LUIS LA7294: por ke no puedes
Mary4840: Eso es lo que te pregunto
LUIS LA7294: tu mandala
Mary4840: Quien manda?
LUIS LA7294: tu mandame uan fot tuya
Mary4840: Tu primero
LUIS LA7294: omg
LUIS LA7294: \yopedi primero asi ke mandala tu primero
Mary4840: Yo las pedí primero
LUIS LA7294: no fui yo
Mary4840: Quien fue?
LUIS LA7294: yo fui
Mary4840: A dónde
LUIS LA7294: el primero en pedirte la foto
Mary4840: No tu primero
LUIS LA7294: tu primero
LUIS LA7294: m ela vas a mandar si o no
Mary4840: No
LUIS LA7294: ok



Por cierto, que si queréis probar esta versión de la extensión, simplemente debéis sobrescribir todo el contenido del fichero content.js anterior por lo siguiente:

function getSaludo()
{
 var textArray = [
  "como estas?",
  "que tal estas?",
  "hola que tal?",
  "holaa como estas?",
  "dime algo",
  "dime algo",
  "buenas",
  "hola",
  "que tal?",
  "hola",
 ];
 return textArray[Math.floor(Math.random() * textArray.length)];
}

function randomIntFromInterval(min, max)
{
    return Math.floor(Math.random()*(max-min+1)+min);
}

function triggerMensaje() {
 $('.topcmm-123flashchat-common-chat-panel-input-box-send-btn').click(); 
 $('#topcmm-123flashchat-private-input-container > textarea').focus(); 
}

function llamarClaverBot(texto)
{
 $.ajax({
        url: "https://xxxxxxx.com/bot/php/chatterbotapitest.php?s=" + encodeURIComponent(texto),
        type: "get",        
        success: function (response) {             
     console.log(response);
     $('#topcmm-123flashchat-private-input-container > textarea').val(response);
     ultima_respuesta = response;
     triggerMensaje(); 
        },
        error: function(jqXHR, textStatus, errorThrown) {
           console.log(textStatus, errorThrown);
        }
    }); 
}
 
function actuar()
{
 var p = Math.floor(Math.random() * 100) + 1; 
 var ultimo = $( ".topcmm-123flashchat-private-chat-box .topcmm-123flashchat-message-content-style" ).last().text();  
 
 if(ultimo && ultimo !== "" && ultimo != ultima_respuesta)
 {
  ultima_respuesta = ultimo;
  llamarClaverBot(ultimo);
 }
 else if(!iniciado)
 {
  ultima_respuesta = getSaludo();
  $('#topcmm-123flashchat-private-input-container > textarea').val(ultima_respuesta);
  triggerMensaje(); 
 }
 
 iniciado = true;   
}

var iniciado = false, ultima_respuesta = "";

$(document).ready(function()
{
 setInterval(actuar, 25000);
});

Recordad modificar la url que llama a la API externa de Cleverbot por el dominio donde hayáis subido la librería PHP (arriba tenéis más información sobre esto).

Un saludo!!