Generar frases de forma automática a partir de plantillas

Ya hemos visto otras formas de generar frases. Esta forma de generar frases consiste en tener unas plantillas de las que sólo algunas partes son variables.

Por ejemplo:

Al abrir el cofre encuentras un ${adjetivo} ${objeto} para ${acción}

Al usar una plantilla las frases generadas tienen sentido y es fácil integrarlas con otras generadas de la misma forma. Los textos generados no tienen tanta variedad como con otros métodos pero te aseguras el sentido de los mismos.

Hay un curioso proyecto en inglés que partiendo de una base de datos de palabras en inglés te permite generar un frase a partir de un aplantilla en la cual solo indicas {{an_ adjetive}}, {{noum}} y el lo rellena con palabras al azar de la base de datos (de términos terroríficos al parecer)

Por ejemplo:

This is {{ an_adjective }} {{ noun }}. => This is a wicked cadaver.

En castellano el principal problema que tiene este método son  distintas flexiones que tiene las palabras y la necesidad de que las distintas palabras de la frase tengan concordancia entre ellas. «Caperucita rojo» es claramente incorrecta. Así que nosotros tenemos que aportar género y número para nombres, complementos, adjetivos. Y en el caso de los verbos persona, tiempo y modo. Para solucionar este problema hay tres opciones:

  • Limitar las opciones, generamos frases solo con un género, número, tiempo y modo.
  • Crear diccionarios diferentes para cada opción. Por ejemplo para los nombres se crearían cuatro diccionarios: femenino-singular, femenino-plural, masculino-singular, masculino-plural.
  • Usar un algoritmo que modifique las flexiones de cada palabra Algo de eso ya hemos visto en este blog. Es un algoritmo al que le pasas una palabra y la forma de la misma que quieres obtener y te la transforma. Ya hemos visto algo así.

En JS generar este tipo de frases es muy sencillo, preparamos una plantilla.

‘Al abrir el cofre encuentras un ${objeto} ${adjetivo} para ${acción}`

Crearemos una lista de opciones:


var adjetivos = ["épico","dorado","brillante"];
var objetos = ["mazo", "mandoble", "arco"];
var acciones = ["matar dragones", "rescatar princesas", "luchar batallas"];

Ahora usaremos una función para elegir uno de ellos al azar:

function selectOne(items){
return items[Math.floor(Math.random() * items.length)];
}

Y finamente otra que rellena la plantilla:

function template(objeto, adjetivo, accion){
return `Al abrir el cofre encuentras un ${objeto} ${adjetivo} para ${accion}`
}

Vamos con el ejemplo de funcionamiento:

template(selectOne(adjetivos), selectOne(objetos), selectOne(acciones));

Ejemplo de resultados:

  • «Al abrir el cofre encuentras un brillante arco para luchar batallas»
  • «Al abrir el cofre encuentras un épico arco para matar dragones»
  • «Al abrir el cofre encuentras un dorado mazo para matar dragones»

Este sistema para generar frases tiene la ventaja de que estas tienen sentido pero tiene la desventaja de que resultan repetitivas y si se usa varias veces en seguida se percibe el patrón.

Un mejora necesaria seria usar un algoritmo de flexiones para encontrar el género y número del objeto y adecuar el del adjetivo. Imaginaros que uno de los objetos es «espada», el adjetivos debería ser femenino.

 

 

Generar frases de forma automática a partir de textos

Ya hemos visto, sin entrar en detalles, un sistema para generar frases en la entrada sobre los bots y las respuestas en lenguaje natural.

La solución más común para generar texto de forma automática es usar cadenas de Markov. Explicándolo pronto y mal consiste en tener un grafo para cada palabra, esa palabra está conectada con el resto de las posibles palabras que pueden seguirla. Cada enlace tiene asociada la probabilidad de que esa sea la siguiente palabra. Además de palabras hay que tener en cuenta dos nodos especiales: «inicio de frase» y «fin de frase». El primer nodo es por el que se empieza para formar una frase y al llegar al segundo se termina la frase.

La forma de usar este grafo es sencilla. Se parte del nodo «inicio de frase». El siguiente nodo se elige al azar, cada nodo tiene una probabilidad distinta de ser elegido. Una vez un nodo es elegido se repite la operación en ese nodo hasta llegar al nodo de «final de la frase».

Generar estos grafos a mano seria demasiado trabajo, por eso estos grafos se crean fácilmente a partir de textos. Tan sencillo como recorrer los textos e ir contando para cada palabra cual es la siguiente. Por ejemplo para la palabra «perro» tomando como base del aprendizaje las siguientes frases:

  • El perro ladró a la bicicleta.
  • El perro lamió la mano de su dueño
  • El perro olisqueó la comida antes de comerla
  • El perro olisqueó la prenda antes de seguir el rastro
  • Como el perro y el gato

El resultado serían:

GrafoPerro

Olisqueó tiene el doble de probabilidades de ser elegida como siguiente palabra que las demás.

El problema que vemos aquí es con las palabras muy habituales como «y». Detrás de «y» puede ir cualquier cosa que no tenga ninguna relación con las palabras anteriores de la frase. ¿Como evitamos eso?. En lugar de generar el grafo de una sola palabra lo generamos de dos o tres. En nuestro caso si lo generamos de dos palabras quedaría así:

El problema de usar más de una palabra es que el tamaño de los textos de aprendizaje ha de ser grande o se repetirán siempre las mismas frases. Vamos a añadir una frase más para que no todas empiecen por «el perro»

  • Al perro le gusta jugar con la pelota

El resultado

 

GrafoPerron

Con más palabras de profundidad las frases tienen más sentido, pero su variedad se resiente. Al final hay que buscar un equilibrio.

Este sistema sirve para generar frases que a veces tienen sentido o que por lo menos dan la sensación de que quieren decir algo. Es importante buscar textos similares para generar grafos que mantengan el sentido. Si por ejemplo mezclamos textos de gatos, animales, con gatos, herramienta, puede ser divertido el resultado pero difícilmente tendrá sentido. Al final el resultado de estas herramientas es más artístico que practico y el texto generado difícilmente tendrá sentido más allá de una frase, aunque si los textos están bien elegidos tendrás la sensación de que quiere decir «algo».

Un ejemplo muy sencillo de usar es esta aplicación cuyo uso es muy sencillo, basta con preparar los textos de ejemplo y pasarlos al generador:

python markov.py gen <name> <count>

  • Name es el nombre del fichero que contiene esos textos
  • Count es el nivel de profundidad de los nodos (el numero de palabras que tiene en cuenta)

Una de la ventajas de este sistema es que solo depende del idioma de los textos usado como fuente, el algoritmo funciona igual en todos los idiomas.

Alarma con nodeMCU y un radar HW-MS03

La razón oficial de este montaje era crear un sistema de alarma con sensor de movimiento que cuando detectase algo me enviara un notificación al movil. La razón real es que me habia comprado un nodeMCU y un HW-MS03 y algo tenia que hacer con ellos.

En principio el proyecto es sencillo. Conectar el radar al nodeMCU y cuadno detecte movmiento enviar una notificación através de un webhook de IFTTT para que apararezca una notificacion en el movil. Pero resulta que nada es tan sencillo como parece, pero asi es más divertido.

Lo primero es concetar el HW-MS03 al nodeMCU lo cual es facil porque solo tiene tres pines.

HW-MS03 nodeMCU Funcion
GND GND Tierra
Vin 3V Alimentacion +3V (ojo no soporta +5V)
Out A0 Señal del HW-MS03 al nodeMCU

Cuando el HW-MS03 detecta algo que se mueve delante suyo (en teoria hasta 4 metros de distancia) manda la señal por el pin out. Basta con detectarla y realizar un http get contra la direccion que facilita IFTTT, facil. He usado al entrada analogica (A0) del nodeMCU porque no estaba segura si el voltaje de la patilla Out bastaria para activar las entradas digitales. Tras varias pruebas he fiajado el valor de umbral en 800.

if(analogRead(A0) > 800){
    ...
}

Pero volvamos un paso atras. resulta que concetar juntos ambas placas fue una mala idea, ambas trabajan en la misma frecuencia. Asi que cuando el nodeMCU esta usando el WiFi el HW-MS03 se vuelve loco. Tras realizar varias pruebas puede ver que esto solo pasa cuando la distancia entre ambos era de unaos 50 cm, pero con cables de esa longituda tambien tenia problemas para leer el estado del pin. La unica opción que me quedaba era encender el WiFi solo para enviar la notificación cuando detectase movimiento. Para ellos hice dos funciones una aque apaga el WiFi y otra que lo conecta. (En realida si que tenia otra opción usar un PIR en lugar de un radar y ya no tendria problemas con el WiFi pero seria menos divertido)

void onWiFi(){  
  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(ssid, password);

  // Wait for connection
  while((WiFiMulti.run() != WL_CONNECTED)) {
    delay(500);
  }
}

void offWiFi(){
  WiFi.disconnect(); 
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
}

Por lo que caundo se detecte movimiento habra que conectar el WiFi, enviarle mensaje y deconectarlo para volver a tener la alarma lista.

if(analogRead(A0) > 800){
  Serial.println("Alarm!!!!!!");      
  onWiFi();
  sendMsg();        
  offWiFi();
  firstStart = true;
}

firstStart es una variable booleana cuya funcion es dar tiempo «a correr» una vez conectada la alarma ya que si no saltaria al detectarte a ti mismo nada más activar la alarma. Tambien permite un tiempo de «descanso» tras el disparo de la alarma para evitar estar dando alarmas sin para y saturar el movil de notificaciones.

if(firstStart){
  Serial.println("Time to hide");    
  delay(60000);
  Serial.println("Watching...");
  firstStart = false;    
}  

Vamos aver como es el codigo que envia la notificacion. He optado por usar el servicio de IFTTT para ello hay que crearse una cuenta alli y activar un webhook, luego se crea una regla que lance una notificacion en el movil. O si se prefiere cualquiera de la opciones que disponibles.

void sendMsg(){
   http.begin("http://maker.ifttt.com/[tu codigo]/alarm/with/key/[pon aqui tu key]"); //HTTP

  int httpCode = http.GET();

  if(httpCode > 0){
    Serial.println("Alarm send");      
  } else {
    Serial.println("Error send alarm");     
  }

  delay(1000);
}

Mientras el nodeMCU este transmitiendo el radar queda inutilizado.

El codigo completo es:

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <ESP8266HTTPClient.h>

const char* ssid = "[tu ssid]";
const char* password = "[password]";

bool firstStart = true;
unsigned long timeNewMeasure = 0;

HTTPClient http;
ESP8266WiFiMulti WiFiMulti;

void setup(void){  
  Serial.begin(115200);
  offWiFi();
  //timeNewMeasure = millis()+1000;
}

void offWiFi(){
  WiFi.disconnect(); 
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
}

void sendMsg(){
   http.begin("http://maker.ifttt.com/[tu codigo]/alarm/with/key/[pon aqui tu key]"); //HTTP

  int httpCode = http.GET();

  if(httpCode > 0){
    Serial.println("Alarm send");      
  } else {
    Serial.println("Error send alarm");     
  }

  delay(1000);
}

void onWiFi(){  
  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP(ssid, password);

  // Wait for connection
  while((WiFiMulti.run() != WL_CONNECTED)) {
    delay(500);
  }
}

void loop(void){
  if(firstStart){
    Serial.println("Time to hide");    
    delay(60000);
    Serial.println("Watching...");
    firstStart = false;    
  }  
  if(millis() > timeNewMeasure){
    timeNewMeasure = millis()+100;
    if(analogRead(A0) > 800){
      Serial.println("Alarm!!!!!!");      
      onWiFi();
      sendMsg();        
      offWiFi();
      firstStart = true;
    }
  }
}

Chispas – Introducción

Antes de explicar qué es chispas voy a tratar de presentar que problema trata de solucionar chispas. Los sistemas «inteligentes» actuales tienen necesidad de elementos externos a tu propia infraestructura. Si compras una bombilla inteligente necesita estar conectada a Internet. Nuestra bombilla inteligente realmente se vuelve bastante tonta si no tiene un «cerebro» que le diga que hacer. ¿Por qué tiene que estar ese cerebro en la nube?. ¿Por qué tengo que depender de una empresa externa para que funcionen las luces de mi casa?. Puede parecer una tontería pero si la compañía cierra o es comprada por otra, como paso hace poco con Revolv, comprada por Nest (la propia Nest había sido comprada por Google) y que dejo tirados a un montón de usuarios de sus productos. ¿Como puede ser que cuando compro algo ese algo no sea mio?. Bueno, es mio como pisapapeles porque gran parte de sus funcionalidades se pierden sin los servicios que le dota una empresa que es la primera interesada en que les compre un pisapapeles nuevo. No soy de naturaleza desconfiada pero me suena sospechoso. Actualmente cualquier electrodoméstico puede durar fácilmente una década. ¿Va a mantener una empresa tanto tiempo los servicios que dotan de inteligencia a tu electrodoméstico?. Ojo, que mantener no es solo tener los servicios funcionando es también ir actualizando y corrigiendo los fallos y errores de seguridad que vayan apareciendo. Todo eso tiene un coste. ¿Van a seguir gastando dinero en un producto que hace tiempo que ya no venden y no les da actualmente ningún beneficio?. ¿Estamos seguro de que van a darnos la mejor solución o solo la más barata?. De hecho ya hay casos de productos con brechas grandes en su seguridad que al no poder, o no querer,  actualizar su firmware no queda más remedio que tirarlos a la basura.

Al problema de depender de empresas externas se suma que a su vez estas contratan los servicios de otras empresas y al final no sabes quién tiene acceso a tus datos. Y no es que estas empresas tengan que tener oscuros intereses en tus datos, pero un fallo de seguridad en cualquiera de ellas puede poner en peligro tu privacidad.

A todo esto hay que añadirle que muchos productos tienen incompatibilidades entre ellos. A veces por motivos técnicos, otras por motivos económicos para encerrarte en su «ecosistema». Y ya no hablo de integrar tus propios desarrollos que muchas veces todo son problemas.

amablemente le dices a tu teléfono «teléfono enciende la luz» y ocurre lo siguiente:

  • Le dices a tu móvil «teléfono enciende la luz»
  • Tu móvil graba tu voz y lo envía a unos servidores en algun otro lugar del mundo
  • Allí procesan tu voz, extraen el texto y lo envían de vuelta a tu móvil
  • Tu móvil procesa el texto y le pasa a la aplicación correspondiente
  • La aplicación le envía la petición al servidor de la empresa que te vendió la bombilla
  • El servidor procesa la petición y le envía a tu bombilla la orden de encenderse
  • Tu bombilla , conectada por WiFi a la red de tu casa, recibe la orden de encenderse
  • Tu bombilla se enciende

Todo ello para sustituir a un trozo de plástico y metal que cierra o abre un circuito según en que posición lo pongas y que a una mala puedes montar con un alambre y un clavo.

¿Chispas va ser la solución de todo este gran problema?. La verdad  es que no. Chispas pretende ser el intento de crear un sistema que facilite la creación de tus propios agentes de software inteligentes. La idea es que funcione en sistemas muy sencillos (y baratos) de tal forma que sea fácil montarte tu . De hecho mi plan es que funcione sobre una raspberry pi, arduino y el navegador de cualquier dispositivo (tablet, movil, PC) que soporten HTML5

Hasta ahora he dicho tres palabras mágicas que suena mucho pero no he explicado en que consisten. Como una imagen vale más que mil palabras la idea es esta:

Esquema0

 

Bueno, quizás lo que valga más que mil palabras sea una buena imagen y no esta. Pero como mis habilidades no dan para más tendremos que apañarnos con lo que hay. El dibujo explica que chispas se compone de:

  • Un servidor web, en este caso una Raspberry Pi, aunque puede ser cualquier ordenador. El único requisito es que pueda servir páginas sobre HTTPS. A su vez puede tener otras funciones como servidor de archivos o base de conocimiento (entendiendo como tal cualquier servicio que aporte información a nuestros agentes)
  • Uno o varios dispositivo con navegador web. El agente web de chispas actúa como intermediario entre el usuario y las funciones de chispas. Al ser una aplicación web no requiere instalación, solo conectarse con el navegador.
  • Chispas firmware funciona sobre uno o varios arduinos. Cada arduino se comporta a su vez como un agente independiente y tiene asociado un servidor web muy sencillo por lo que desde un navegador podrias conectarte directamente al arduino y controlarlo desde la interface que sirva el propio arduino. Para que esto funcione el arduino debe de ser capaz de conectarse a la red ya sea usando una shield, un desarrollo propio, o alguna placa que incorpore la conexión a ethernet

Cada uno de estas partes es independiente de las demás y trabaja de forma colaborativa entre ellas. si por ejemplo fallara la raspberry los arduinos seguirían funcionando y el agente web seguiría pudiendo interactuar con ellos.

Todo funciona sobre la red local con intercambio de peticiones HTTP. Es una arquitectura simple y que hace muy sencillo poder introducir componentes propios a cualquier nivel. Nada es propietario y cualquier parte es prescindible o modificable. No se depende de servicios externos pero eso no te impide conectar a Chispas a los mismos. Por ejemplo si queremos que reaccione a nuestro email nada nos impide poner en la raspberry código que lo haga y enviar esa información a los agentes.

En resumen, sencillo, barato, abierto y extensible.