Simular la curva del olvido

Ya hemos visto como hacer algoritmos que «olvidan» con el tiempo, es decir que daban mayor relevancia a los valores recientes que a los más antiguos. Ahora vamos a fijarnos en un modelo de como el cerebro olvida para intentar simularlo. Es un modelo bastante simplificado, proviene de distintos experimentos prácticos basándose en los realizados por Hermann Ebbinghaus estos experimentos parten de memorizar una lista de palabras o silabas sin relación entre ellas. Para lograr que fuera un ejercicio puro de memoria los primeros experimentos usaban listas de silabas inventadas. El sujeto memorizaba la lista y a intervalos de tiempo se pedía recordar todas las que pudiera y se iba contando las que olvidaba.

De estos experimentos se han sacado varias formulas que tratan de aproximar el comportamiento del olvido. Actualmente se acepta como una buena aproximación la formula:

R = e (-t/S)
  • R es el porcentaje de la palabras que se recuerda
  • e es, pues eso, el número e
  • t es el tiempo
  • S la intensidad del recuerdo
e ^-t/S En azul S = 0.5 En verde S = 1

Esta ecuación es la misma que ya vimos en el post sobre algoritmos que olvidan. Solo que allí la formula tenia otra forma:

R = e -kt

En este caso k = 1/S . Esto le confiere propiedades muy útiles que luego veremos.

Modelo Básico

Vamos a empezar por simular el caso más sencillo. Un listado de sílabas aleatorias como el del experimento. En este caso el parámetro S es para todas el mismo. Y todas tiene la misma probabilidad de ser olvidadas en el momento T.

Para saber si una palabra se sigue recordando o no en el momento T se calcula un número al azar entre 0 y 1. Si el número es mayor que el valor de e ^ -t/S esa palabra se olvida. Si es menor se recuerda. Solo queda repetir este proceso por cada palabra del documento.

Mejorando el modelo

Lo que ocurre es que pocas veces en la vida hemos de memorizar un texto de esas características. Lo normal es que sea un texto con distintos tipos de palabras. Veamos como modificamos el parámetro S de cada palabra.

Las palabras vacías o stopwords que son palabras que no aportan nada al significado del texto deberían de tener una S muy baja para que se olviden rápidamente. Para este punto necesitaremos un listado de palabras vacías.

Los nombres propios, fechas, lugares y números incrementan el valor de su S para que duren más tiempo. Este punto requiere la extracción de nombres, entidades, fechas y números. Para ello se pueden combinar distintos algoritmos con diccionarios.

Cada vez que una palabra repita en el texto se incrementa un poco el valor de su S. Salvo con las palabras vacías que no incrementan nada su valor.

Las palabras propias del tema del texto incrementan su valor de S un poco. Es decir, si el texto es sobre medicina las palabras relacionadas con la medicina se recuerdan más fácilmente. Esta parte requiere diccionarios de términos de cada tema.

El caso más complicado es el de las palabras nuevas o desconocidas que deberían tener una S menor de la que les corresponde. La mitad, por ejemplo. Este punto es difícil de simular en un ordenador, podemos hacerlo dotándole de un listado de palabras conocidas.

Este algoritmo produce como resultado un texto que pasado cierto tiempo va olvidando de forma aleatoria distintas partes con preferencia por recordar los datos del texto más relevantes y relacionados con la materia del mismo y el conocimiento previo del lector.

Simulando el aprendizaje por repetición

Una de las conclusiones que se sacaron de estos estudios es que mientras no se ha olvidado el texto el volver a memorizarlo cuesta menos tiempo, es lógico pues aún recordamos parte de él y que cada vez su recuerdo permanece más tiempo.

Esto se podría simular haciendo que cada vez que sea «lea» el texto el valor de la S calculado en el apartado anterior se aumente en un 40% o 50%. Sn = S * (1+(0.5*n)) siendo n el número de relecturas del texto.

Buscar palabras por como empiezan o terminan

Buscar palabras completas usando un hashmap es sencillo. Pero cuando has de buscar por parte de la palabra la cosa se complica. ¿Como buscar todas las palabras que empiezan o terminan por una cadena dada?.

La solución es usar un trie. Un tipo de estructura de datos en la que básicamente creamos un árbol donde cada letra es un nodo. Por ejemplo el trie formado por las palabras: cardo, cardos y carne sería:

Las terminaciones «end» indican que ahí termina una palabra. Si se toma desde esa terminación y se asciende por los padres se forma la palabra almacenada. Por ejemplo: end2 O D R A C representa la palabra CARDO.

Para buscar las palabras que empiezan por un conjunto de letras se recorre en el trie ese conjunto de letras y a partir del último nodo del conjunto se recorren todas las ramas reconstruyendo las palabras cuando encuentra un indicador de fin de palabra.

Por ejemplo si buscamos palabras que empiezan por «card«:

  • CARD O S end
  • CARD O end

Para buscar palabras que terminan el método es el mismo solo que hemos de meter la palabra al revés en el trie. En lugar de guardar cardo guardamos odrac. Al recuperarla del trie le volvemos a dar la vuelta y listo.

Al buscar es necesario invertir la cadena de búsqueda. Es decir si se buscan palabras terminadas en do hay que buscar la cadena od

Hasta aquí la teoría, ahora veamos la diferencia con la realidad. El principal problema son las tildes y las diéresis. Cuando buscas bru esperás que aparezca tanto bruja como brújula pero el trie no distingue entre u/ú ¿Cómo solucionamos ésto? Haciendo un proceso de limpieza de las palabras al introducirlas en el trie.

Para que la palabra correcta no se pierda se almacenará junto con el indicador de palabra completa. En nuestro ejemplo:

  • End1 – Cardos
  • End2 – Cardo
  • End3 – Carne

Con esto ganamos velocidad ya que nos ahorramos el proceso de reconstruir la palabra, a cambio ocupa más espacio en memoria.

Hay que tener en cuenta que en una misma rama puede haber varias palabras como medico y medicó, por lo que hay que almacenar un listado de palabras.


Calcular la dificultad de lectura de una palabra

Hacer un algoritmo que calculé lo difícil de leer que es una palabra es complicado, depende de muchos factores, algunos propios del lector: cultura, velocidad, conocimiento del tema, problemas con algunas sílabas,… Por lo que el valor obtenido hay que tomarlo como un indicador y no como un valor absoluto

En nuestro modelo vamos a asociar la dificultad para lectura con la dificultad para pronunciar. Por ello descompondremos la palabra en sílabas y calcularemos la dificultad de cada una.

Dificultad de lectura de una sílaba

El modelo que usaremos puntúa con valor númerico a cada letra de la silaba, además suma o resta puntos por ciertas combinaciones de letras. El resutado final de sumar todo da la dificultad de lectura.

  • Las vocales son más faciles de leer que las consonantes. (+1)
  • La h al ser muda no afecta a la pronunciación. (+0)
  • Las consonantes habituales tienen una dificultad media (+2)
  • Hay consonantes poco comunes que hacen más dificiles la pronunciciación debido a la falta de habito. (+3)

Valores de cada letra:

  • +0: h
  • +1: a,e,i,o,u
  • +2: b,c,d,f,g,j,l,m,n,ñ,p,q,r,s,t,v
  • +3: k,w,x

Hay excepciones en las que una letra no cuenta, por lo que en esos casos esa letra no aporta valor, para ello se realizaran los siguientes remplazos:

que = qe
qui = qi
gui = ge
gue = ge
rr = r
ll = y

Dos consonantes seguidas complican la pronunciacion:Si la segunda consonanete es «hbrls» +1Si  la segunda consonanete es cualquier otra +2

Dificultad:

Baja1-3a, be, ca
Media4-6del, bra, tri
Alta7-…tras, blas


Problemas y mejoras:

  • No sirve para otros idiomas.
  • Considera que todo es valido, por ejemplo «mnztr» puntua alto pero lo considera «legible»

Dificultad de lectura de una palabra

Para saber la dificultad de una palabra basta con sumar la dificultad de cada una de sus sílabas. Aunque se puede usar el valor medio (dividiéndolo entre el número de sílabas o de letras) yo recomiendo usar el valor absoluto ya que así se refleja la longitud de la palabra. Por ejemplo «catarata» a mí me parece más difícil que «cabra». Si usamos el valor absoluto catarata puntúa 12 y cabra 9. En valor medio 3 y 4,5. 

El primer problema es dividir la palabra en sílabas, por suerte aquí tienes una solución. 

Ahora se puede valorar cada sílaba y sumarlas. Para saber qué dificultad tiene cada palabra se pueden usar los siguientes valores que he calculado
a prueba y error:

Baja1-9
Media10-13
Alta14-…

Problemas y mejoras:

  • Los plurales, simplemente añadir una s o una es al final puede hacer que una palabra suba su dificultad cuando muchas veces no se corresponde con el incremento «real» de la dificultad.

  • Hay sílabas que cuando van seguidas aumenta la dificultad de pronunciarlas, en este modelo no se ha tenido en cuenta.

Hacer un sistema de lectura rápida

En esta entrada voy a dar los pasos para crear un sistema de lectura rápida tipo spritz o spritzlet.

Estos sistemas se basan en poder leer de un vistazo sin mover los ojos. Para ello nos muestran las palabras una a una centradas de forma adecuada para no tener que mover los ojos. Con algo de práctica llegas a un punto en que lees las palabras casi sin darte cuenta. En mi experiencia personal puedo decir que en textos complejos cuesta seguir el hilo. Pero para lectura de textos sencillos o para una primera lectura resulta muy útil.

Punto de reconocimiento óptimo (ORP)

El primer paso es separar el texto en tokens. Usaremos los espacios y fin de línea como separadores.

Para cada token hemos de calcular el «punto de reconocimiento óptimo» (Optimal Recognition Point) ORP. Que es el punto donde se fijan los ojos al leer una palabra. Su cálculo es sencillo, es suficiente con saber la longitud de la palabra: (en la tabla la posición está indicada empezando a contar el primer carácter como posición 0)

LongitudPosición ORP
10
21
31
41
51
62
72
82
92
103
113
123
133
14+4

Con el ORP calculado hemos de calcular el tiempo que se mostrará ese token en pantalla. Ajustando el tiempo por carácter más un pequeño extra para cambiar de palabra y una parada un poco más larga en los signos de puntuación (o se pueden mostrar estos de forma individual).

Interfaz de usuario

Con eso ya tenemos todo lo necesario para mostrar la palabras una por una. Para ello elegiremos un punto de la pantalla donde no suponga un gran esfuerzo mirar fijamente. Por lo general o el centro de la pantalla o un poco por encima de este.

Hay que elegir unos colores para el texto y su fondo que se lean correctamente. Podemos oscurecer el resto de la pantalla para que no distraiga.

¿Os acordáis del ORP? Ahora hay que asegurarse de que todas las palabras se presentan centradas en ese carácter. Puede ser buena idea marcarlo de alguna manera, generalmente se marca en algún color llamativo, aunque usar alguna ayuda más como subrayar o ponerla en negrita permite que sea más accesible para personas que no distingan colores o desde dispositivos que sean en blanco y negro.

Algunas versiones cambian el tamaño de las letras según lo que ocupe la palabra, pero a mí esa opción me parece incómoda cuando saltas de palabras pequeñas a muy largas y eso supone un gran cambio del tamaño de letra.

Tiempo de lectura

Es necesario permitir al lector ajustar la velocidad de lectura en palabras por minuto. Habitualmente la velocidad se permite ajustar de 100 a 700 palabras por minuto.

Para calcular el tiempo que hemos de tener cada palabra en pantalla hay dos formas. Tener todas las palabras el mismo tiempo en pantalla, para ello se calcula el tiempo por palabra dividiendo 60 segundos entre el número de palabras por minuto seleccionado. Es un sistema simple y que da buenos resultados. Si se quiere hilar un poco más fino se puede calcular el tiempo por letra, para ello se supone que el tiempo por palabra se refiere a una palabra media de 5 letras y se divide por 5 para obtener el tiempo por letra, ahora se multiplica este tiempo por el total de letras en la palabra y así se obtiene el tiempo que tiene que mostrarse la palabra. Es buena idea fijar un tiempo mínimo o en casos de lecturas muy rápidas las palabras muy cortas podrían ser difíciles de leer.

Palabras muy largas

Aquí tenemos un problema. Si la palabra es muy larga, algo del estilo de «esternocleidomastoideo», mostrarla de una sola vez obliga a mover los ojos. Hay que dividirla, lo recomendable es no pasar de las tres o cuatro sílabas. Pero ojo, no partas sílabas, eso complica la lectura de la misma. Es recomendable señalar de alguna forma que el texto que muestras es parte de una palabra. Compara:

«Esternoc» «leidomast» «oideo»

«Esterno~» «~cleidomas~» «~toideo»

Conclusión

Aunque, dada la cantidad de librerías y aplicaciones que implementan este sistema de lectura spritz, no es necesario programar desde cero un lector rápido las ideas aquí expuestas pueden servir para entender como funciona y para aplicarlas a otras funcionalidades.

Detectar nombres propios en un texto

En el análisis de un texto detectar los nombres propios es importante ya que aporta información de que se habla en el texto. Permite extraer nombres e personas empresas y de hecho es una tarea habitual en muchos sistemas inteligentes de gestión de documentos.

Empiezan por mayúscula

Detectar nombres propios en español parece una tarea sencilla, lo primero que se nos ocurre es: «Empiezan por mayúscula» y es cierto, cualquier palabra que empiece por mayúsculas en un texto es sospechoso de ser un nombre propio. As que nuestro primer paso es recopilar esas palabras.

No es suficiente con que empiece por mayúscula. Cualquier palabra al principio de una oración también empieza por mayúscula y precisame el principio de la oración es un lugar muy habitual para los nombres (propios o comunes). En ese caso la mayúscula no servirá de pista. Es necesario obtener la información de si una palabra «sospechosa de se un nombre» va o no al principio de una frase.

Pero no es el único caso en que una palabra que no sea un nombre propio empieza por mayúscula. Siglas, meses, días de la semana, notas musicales, cargos, períodos históricos….. Hay muchos casos en los que correcta o erróneamente (los días de la semana no deberían ir en mayúsculas pero no es raro verlos así) se suelen poner la letra inicial en mayúscula.

Siglas y acrónimos

Como caso especial están las siglas y acrónimos. El problema aquí es distinguir los que generalmente indican nombres propios de otros que no lo son, por ejemplo ONU, OTAN, USA son nombres propios mientras que SOS, WTF, OMG no lo son. Para distinguir siglas del resto de palabras de un texto es sencillo, están completamente escritas en mayúsculas y a veces tiene puntos entre ellas. Sin embargo, por motivos esteticos, puede darse el caso de encontrar siglas con minúsculas, sobre todos cuando son nombres de empresas.

Números Romanos

Son fáciles de confundir con las siglas. De hecho a veces son indistinguibles, por ejemplo CC puede ser 200 o significar «con copia».

Los números romanos van sin punto al final y solo pueden contener las letras M,D,C,L,X,I.

Diccionarios

Necesitamos dos diccionarios, uno de palabras comunes que van en mayúsculas y no son nombres y otro de nombres.

Si una palabra empieza por mayúscula y está en el diccionario de nombres se puede considerar un nombre. Este diccionario a veces es difícil de crear ya que no se sabe que nombres se buscan, sin embargo cuando se sabe exactamente lo qué se busca puede ser una herramienta muy potente.

El diccionario de términos que pueden ir en mayúsculas y no son nombres es más genérico. Está formado por palabras que se suelen escribir en mayúsculas y otras que pueden ir al principio de una frase. Podemos incluir:

  • Meses del año
  • Días de la semana
  • Festivos
  • Notas musicales
  • Abreviaturas
  • Títulos, cargos y nombres de dignidad
  • Signos del zodíaco
  • Puntos cardinales
  • Siglas y acrónimos de nombres comunes o expresiones
  • Palabras que a veces se escriben con mayúsculas: patria, gobierno, biblia, dios,….
  • Onomatopeyas
  • Determinantes
  • Pronombres

Ojo que hay casos en que un nombre propio puede confundirse con alguno de los casos anteriores. Por ejemplo: Abril o Domingo son nombres de personas.

Hay que tener en cuenta que en el caso de los nombres compuestos pueden empezar por alguno de los elementos que están en este diccionario. Por ejemplo: «El Dorado, Los Ángeles»

Hay una mejora al diccionario de nombres y es añadir dinámicamente los nombres que vamos encontrando en el texto. Los nombres generalmente se repiten varias veces en el texto y puede ayudar a despejar las dudas sobre alguna palabra.

Palabras dudosas

Vamos a repasar los pasos que tenemos hechos hasta ahora.

  1. Seleccionar palabras que empiezan por mayúscula.
  2. Del paso anterior seleccionar aquellas que no están detrás de un punto o al inicio de una frase.
  3. Descartar excepciones usando reglas (como los números romanos) o el diccionario de palabras en mayúsculas que no son nombres.
  4. Las restantes del paso anterior se consideran nombres.
  5. Se añaden al diccionario de nombres
  6. Se toman las palabras del paso 1 que van detrás de un punto o al principio de una frase.
  7. Si están en el diccionario de nombres se consideran nombres.

Tras estos pasos nos quedan un conjuntos de palabras que no sabemos si son nombres o no. Podemos usar alguna heurística más como considerarlo un nombre si van dos palabras con mayúsculas seguidas o si son siglas.

Al final nos quedan palabras que dudamos si son nombres o no. Si el texto ha de pasar a revisión por un humano lo mejor es marcarlos como «posibles nombres», si no hay más opciones que marcarlo como nombre o no hay que valorar que opción es menos costosa, detectar como nombres palabras que no lo son o dejarse nombres sin detectar.

Nombres compuestos

Por ahora hemos tratado los nombres como si solo fueran una palabra, pero eso es muy irreal, gran cantidad de nombres están compuestos por más de una palabra. Para detectarlos vamos juntar todos aquellas palabras que empiecen por mayúscula y vayan seguidas (sin otras palabras o signos de puntuación entre ellas).

Si una de esas palabras cumple los requisitos para ser nombre se extiende a las demás palabras en mayúsculas que la rodean.

Mejoras

Uno de los elementos más importantes de este enfoque son los diccionarios. Cuando el sistema solo va a trabaja con textos de una temática concreta unos buenos diccionarios pueden dar resultados muy exactos.

Algo parecido pasa con reglas propias para textos de ciertos ámbitos. Por ejemplo si se buscan nombres de empresas se podría usar como pista que terminen en «S.A.», «S.L.», «INC», …

Hay nombres como los títulos de libros o películas que entrañan bastante dificultad ya que las reglas ortográficas dicen que tienen que ir en mayúsculas solo la primera palabra. Suelen ir entrecomilladas, en cursiva o en negrita, pero aún así no es un problema sencillo.

Procesar un texto carácter a carácter

Una de las formas más básicas y relativamente simple para procesar texto es el análisis de carácteres uno a uno. Es un sistema computacionalmente muy simple y que se puede ejecutar con muy poco coste, tan poco que yo he llegado a usarlo en un Arduino con una shield ethernet para procesar peticiones HTTP. El proceso consiste en analizar el texto carácter a carácter clasificando cada carácter en uno o varios tipos y aplicar reglas según esos tipo.

Un ejemplo de tokenizador muy simple sería un sistema que clasifica los caracteres en letras (a-zA-Z), números (0-9) y símbolos (el resto).

for char c in text {
  if tipo(c) == tipoAnteriorSimbolo {
     token += c;
  } else {
    guardarToken(token);
    token = c;
  }
  tipoAnteriorSimbolo = tipo(c);
}

Múltiples tipos por carácter.

El sistema anterior puede ser útil para tratar con lenguajes artificiales muy estrictos como como  ser el uso de comandos o protocolos, pero tiene problemas con lenguajes más «humanos». Por ejemplo si tratamos de sacar los números de la frase : «Debe 1.000,00€ desde el día 10 de junio.» obtendremos «1», «000», «00», «10». Cuando lo correcto es: «1.000,00» y «10». Podemos ver en este caso como hay caracteres que no son números pero deberían considerarse como tales «.,» . Una solución es que en lugar de considerar cada carácter de un solo tipo que puedan ser de varios. En este caso los caracteres «.,» pueden ser considerados signo de puntuación y número.

for char c in text {
  if tipos(c).estaEn(tiposAnteriorSimbolo) {
     token += c;
  } else {
    guardarToken(token);
    token = c;
  }
  tiposAnteriorSimbolo = tipo(c);
}

Ventana de caracteres

Cambiemos de caso, ahora miremos el texto: «Quedamos a las 10. No vino». Aquí el problema es que nuestro sistema vera «10.» como un número, vale que luego en un procesamiento posterior podemos eliminar el punto que sobra. Pero también podemos solucionar ese problema aumentando «la ventana» que ve el algoritmo. Actualmente el algoritmo juzga el carácter actual y el anterior, se puede modificar para que además vea el siguiente carácter, de tal forma que disponga de los tipos de los tres caracteres para comparar.

Luego hay que definir reglas. Por ejemplo para que un punto cuente ha de estar rodeado de otros dos números. Si está rodeado de un número y un espacio el punto no forma parte del número.

Ventajas e inconvenientes

La principal ventaja de este algoritmo son los pocos recursos que necesita para ejecutarse. Para casos limitados puede funcionar perfectamente. Por eso puede ser ideal para usar en microcontroladores.

Su inconveniente es que no es suficiente flexible para procesar lenguajes humanos y que las reglas pueden llegar a ser muy complicadas.

Sin embargo en caso de que no puedas procesar algoritmos más complicados puede ser la única posibilidad.

Problemas con procesamiento de lenguaje natural en Español

Todos los lenguajes tienen sus particularidades que resultan problemáticas a la hora de ser procesados por un algoritmo. Pero como yo me he centrado en el español puedo resumir los problemas que me he encontrado a distintos niveles del proceso. Estas son las características más habituales que se atragantan a la hora de procesar el español:

Verbos

Nuestros verbos son un horror para muchos estudiantes y para su procesamiento por las máquinas. Literalmente cientos de formas verbales según el tiempo, número, género, aspecto y modo. Formas compuestas, perífrasis verbales, pronombres clíticos y cuando tienes todas las reglas modeladas descubres la cantidad de verbos irregulares que hay.

Tildes

aquí la «diversión» viene por dos lados. Primero que para algunos algoritmos hay que tenerlas en cuenta, para otros no. Por ejemplo para los stemmer no se tienen en cuenta para saber la conjugación verbal hay que considerarlas. Por otro lado «olvidarse» de ponerlas es muy habitual (pueden usarse como ejemplo cualquiera de mis post) y más ahora que puede ser correcto escribir la misma palabra con o sin tilde.

Ñ

La ñ, la mayoría de los lenguajes de programación y librerías están pensadas para el alfabeto inglés, por lo que la ñ (al igual que las vocales con tilde) es la gran olvidada del alfabeto español.

Flexiones

Dependiendo la función que haga una palabra puede tener distintas flexiones y hay que tenerlas en cuenta todas. Por ejemplo si quiero buscar referencias a colores hay que tener en cuenta la palabras «rojo, roja, rojos, rojos, rojizo, rojiza, rojizas, rojizos»

Las reglas que hay tiene gran cantidad de excepciones y hay que contemplar otros casos como términos cuyas palabras son distintas para cuando cambias de número o genero (toro – vaca), que las reglas aplican pero el significado es distinto (rata – rato) o con múltiples formas para la misma flexión como puede ser los aumentativos (-azo, -ton) y diminutivos (-ita, -illa, -ica) que además pueden cambiar el sentido de la palabra (listo – listillo) complicándolo todo un poco más.

Reglas poco estrictas para formar frases

Libertad al configurar las frases. Nuestras reglas para componer frases no son muy estrictas. Lo mismo podemos decir «El coche rojo de Juan», «Es rojo el coche de Juan».

Variantes

El español está muy extendido por todo el mundo por lo que hay distintas variantes. Pero como vivimos en un mundo muy interconectado es fácil encontrar esas variantes mezcladas, sobre todo en Internet.

Algoritmo para inventar palabras

Jugando con el lenguaje vamos a crear un algoritmo que invente palabras. Pero no el típico algoritmo que elige letras al azar. La idea es que genere palabras que parezcan españolas o al menos que se puedan leer sin problemas. Lo que se conocen como jitanjáforas . Para eso hay que seguir una reglas, empezaremos creando sílabas «válidas». Una forma de hacer esto es usa ngram de letras. Ya vimos su uso para generar textos. El principio es el mismo pero usando letras en lugar de palabras. Como ya hemos visto ese método, en esta entrada usaremos una técnica diferente que permite mayor personalización (y así no me repito).

Inventando sílabas

Las sílabas que construyamos tienen que ser habituales en el español. Para facilitar nuestra tarea vamos a evitar generar sílabas que nos puedan dar problemas.

Seguiremos las siguientes reglas:

  • Omitiremos los caracteres raros en el español: k, x, w
  • Las vocales sueltas, solo las permitiremos cuando sea la primera silaba de la palabra: a, e, i, o, u
  • Las sílabas serán consonante + vocal
  • Hay consonantes que pueden ir acompañadas de una segunda consonante antes de la vocal:
b[r,l]
c[h,r,l]
f [r,l]
g[r]
p[r]
r[r]
t[r]
  • Tras la vocal pueden llevar las consonantes n,s. 
  • Un caso especial, la ‘q’ que prácticamente siempre va seguida de ‘u’ de hecho nosotros solo permitiremos dos formas: que, qui
  • Otro caso especial es la ‘g‘ cuando va seguida de la ‘u‘ dando lugar a ‘gu‘ que puede ir seguida de otra vocal. Solo contemplaremos la posibilidad de las formas: gua, gue, gui

Expresiones regulares

Vamos a usar un generador de textos aleatorios a partir de expresiones regulares. Para ello usa una expresión regular como plantilla para generar un texto. La librería que usaremos es esta, se puede probar directamente desde la web sin instalar nada.

Todas las regla de arriba se codifican en forma de una expresión regular como:

([aeiuo]|[])(((b[rl]?([aeiuo]|[ae]))|(c[rhl]?([aeiuo]|[ae]))|(d[r]?([aeiuo]|[ae]))|(f[r]?([aeiuo]|[ae]))|(p[r]?([aeiuo]|[ae]))|(r[r]?([aeiuo]|[ae]))|(t[r]?([aeiuo]|[ae]))|(s([aeiuo]|[ae]))|([vhjkñz]([aeiuo]|[ae]))|(l([aeiuo]|[ae]))|(m([aeiuo]|[ae]))|(n([aeiuo]|[ae]))|(qu[ei])|((gu[ae])|(g([aeiuo]|[ae]))))([nrs]|[]|[])){2,5}

Así toda de una vez y sin explicar se puede atragantar un poquito

([aeiuo]|[]) – empieza por vocal o no

((b[rl]?([aeiuo]|[ae]))|(c[rhl]?([aeiuo]|[ae]))|… – puede ser b o c (o d o e….)

((b[rl]?([aeiuo]|[ae])) – b puede ir seguido o no de r, seguido de una vocal

([nrs]|[]|[]) – la silaba va seguida de n,r,s o nada

{2,5} – de 2 a 5 silabas

Ajustar las probabilidades

La a y la e son mucho más probables que la i,o,u. Sin embargo la expresión regular aplica la misma probabilidad para todas las letras El truco está en como funciona el operador ‘|‘ y el ‘[]’.

Por ejemplo: [ae]|[aeiou]

Lo que hay a cada lado del | tiene un 50% de probabilidades. En el lado de la derecha la ‘a‘ aparece con un 50% de probabilidades y en el de la izquierda con un 20%. Dando para la ‘a‘ una probabilidad del (0.5*0.5)+(0.5*0.2)=0.35

Para todas las vocales las probabilidades son:

a (35%) , e (35%) , i (10%), o (10%), u (10%)

El mismo truco se usa en otras expresiones como: ([nrs]|[]|[])

Correcciones

Tras generar las palabras hay que aplicar algunas correcciones:

  • Si empieza por ‘rr’ se cambia a ‘r’
  • Si tiene ‘nb‘ o ‘np‘ se cambian por ‘mb‘ y ‘mp

Reconocer el idioma de un texto

Los diferentes idiomas tienen características distintas por lo tanto cuando nos enfrentemos a realizar cualquier aplicación que use reconocimiento del lenguaje en múltiples idiomas lo primero será reconocer el idioma para saber que reglas aplicar.

Vamos a usar franc

Originalmente es una herramienta para node aunque tiene versión para navegador que se puede descargar aquí.

Una vez cargada la librería está tiene dos formas de usarse:

var lang = franc('hola es to es una prueba);                

Devuelve el idioma más probable.

 var langs = franc.all('hola esto es una prueba);                

Devuelve un listado con todos los idiomas y la probabilidad de que sea cada uno.

Como funciona

Los sistemas que reconocen el idioma básicamente se basan en buscar grupos de caracteres (n-gramas) representativos de ese idioma. Para ello se les entrena con diversos documentos.

En el fichero data.json de franc tenemos los datos que se han obtenido del proceso de aprendizaje, en este caso trigramas. Como curiosidad incluyo los del español:

de|os |de | la|la | y | a |es |ón |ión|rec|ere|der| co|e l|el |en |ien|cho|ent|ech|ció|aci|o a|a p| el|a l|al |as |e d| en|na |ona|s d|da |nte| to|ad |ene|con| pr| su|tod| se|ho |los| pe|per|ers| lo|o d| ti|cia|n d|cio| es|ida|res|a t|tie|ion|rso|te |do | in|son| re| li|to |dad|tad|e s|est|pro|que|men| po|a e|oda|nci| qu| un|ue |ne |n e|s y|lib|su | na|s e|nac|ia |e e|tra| pa|or |ado|a d|nes|ra |se |ual|a c|er |por|com|nal|rta|a s|ber| o |one|s p|dos|rá |sta|les|des|ibe|ser|era|ar |ert|ter| di|ale|l d|nto|hos|del|ica|a a|s n|n c|oci|imi|io |o e|re |y l|e c|ant|cci| as|las|par|ame| cu|ici|ara|enc|s t|ndi| so|o s|mie|tos|una|bre|dic|cla|s l|e a|l p|pre|ntr|o t|ial|y a|nid|n p|a y|man|omo|so |n l| al|ali|s a|no | ig|s s|e p|nta|uma|ten|gua|ade|y e|soc|mo | fu|igu|o p|n t|hum|d d|ran|ria|y d|ada|tiv|l e|cas| ca|vid|l t|s c|ido|das|dis|s i| hu|s o|nad|fun| ma|rac|nda|eli|sar|und| ac|uni|mbr|a u|die|e i|qui|a i| ha|lar| tr|odo|ca |tic|o y|cti|lid|ori|ndo|ari| me|ta |ind|esa|cua|un |ier|tal|esp|seg|ele|ons|ito|ont|iva|s h|d y|nos|ist|rse| le|cie|ide|edi|ecc|ios|l m|r e|med|tor|sti|n a|rim|uie|ple|tri|ibr|sus|lo |ect|pen|y c|an |e h|n s|ern|tar|l y|egu|gur|ura|int|ond|mat|l r|r a|isf|ote


Si alguien se molesta en mirarlos puede darse una sorpresa. ¿Dónde está la «ñ»?. La «ñ» es un carácter muy representativo del español pero aparece muy poco por eso el algoritmo no la ha «aprendido» como representativa en ningún trigrama. Esos casos quedarían como una mejora que seria considerar caracteres propios de ese idioma. Aunque en muchos casos no aparecerán más que en textos largos por lo que no aportarán demasiado a los resultados y podría inducir a error si por ejemplo hubiera un nombre de un idioma con unos de esos caracteres en un texto de ese idioma.

Convertir de números a palabras. Cardinales

Pasar de números a palabras es una tarea bastante sencilla. En esta entrada vamos a ver los cardinales y dejaremos los ordinales para otra.

Los números en castellano repiten la forma de nombrarse cada tres dígitos: Unidades, decenas, centenas. Si sabes decir los números del 0 al 999 sabes decir todos los demás sólo hay que añadir detrás el valor correspondiente: mil, millón, billón, trillón,… Hay que recordar que en español el mil se repite entre millón, billón, trillón,…

Cómo la forma se repiten el primer paso será separar el número en bloques de tres dígitos y escribir su valor correspondiente. En español hay dos maneras válidas de componer el texto que representa a estos tres dígitos:

centenas + » «+decenas+»i»+unidades

centenas + » «+decenas+» y «+unidades

Nosotros usaremos la segunda por ser la más frecuente. Aunque los números menores de 30 se realizan con la primera forma.

Centenas:

100 ciento *
200 doscientos *
300 trescientos *
400 cuatrocientos *
 500 quinientos *
600 seiscientos *
700 setecientos *
800 ochocientos *
900 novecientos *

Decenas:

10 dieci*
20 veinti*
30 treinta y *
40 cuarenta y *
50 cincuenta y *
60 sesenta y *
70 setenta y *
80 ochenta y *
90 noventa y *

Unidades:

1 uno
2 dos
3 tres
4 cuatro
5 cinco
6 seis
7 siete
8 ocho
9 nueve

Excepciones:

0 cero
10 diez
11 once
12 doce
13 trece
14 catorce
15 quince
20 veinte
100 cien

Con esto ya podemos decir todos los números. Cada tres números hay que añadir un indicador de valor:

*.000 * mil
*.000.000 * millones
*.000.000.000 * mil (millones)
*.000.000.000.000 * billones
*.000.000.000.000.000 * mil (billones)
*.000.000.000.000.000.000 * trillones

Aquí nos encontramos con las excepciones cuando X es 1

1.000 mil
1.000.000 un millón
1.000.000.000 mil millones
1.000.000.000.000 un billón
1.000.000.000.000.000 mil billones
1.000.000.000.000.000.000 un trillón

Parece muy complicado pero son unas pocas reglas muy sencillas de codificar.

De palabras a número

Como acabamos de ver nuestro sistema de nombrar los números se basa en repetir bloques de tres números y añadir un modificador de valor (miles, millones,…). El proceso de transformar texto a número consiste en:

  1. iniciar la variable number a 0
  2. ver si el texto empieza por alguna de las palabras de las tablas anteriores
    1. Si la palabra representa centenas, decena o unidades se sumar el valor correspondiente a number.
    2. Si la palabra es un modificador de valor se multiplica por el valor que representa
  3. eliminar palabras encontrado del principio del texto
  4. volver al punto 2 hasta que se termine el texto o no se encuentren coincidencias

Antes de tratar el texto y para facilitarnos el procesado pasamos el texto a minúsculas, eliminamos de la cadena, las tildes, espacios y reemplazamos las «y» por «i».

«Veintidós mil trecientos quince»

Primero tratamos la cadena;

«veintidosmiltrecientosquince»

Buscamos la primera coincidencia

veinti -> number +20
dos -> number +2
mil -> number *1000
trescientos -> number +300
quince -> number +15

number = 22315

Podéis ver le código aquí.

De número a palabras

Para hacer el paso de número a palabras hay que dividir el numero en bloques de tres dígitos y tratar cada uno de forma independiente usando las tablas del principio

123456

123 – «ciento» + «veinti» + «tres»

456 – «cuatrocientos» + «cincuenta» + «y» + «seis»

Añadimos el modificador de valor correspondiente

«ciento veintitres» + «mil»

«cuatrocientos cincuenta y seis»

Unimos las cadenas:

«ciento veintitres mil» + «cuatrocientos cincuenta y seis»

Resultado:

ciento veintitres mil cuatrocientos cincuenta y seis

Queda un último paso que es reemplazar las excepciones, por ejemplo «uno mil» por «mil» o «veintitres» por «veintitrés» (como en este caso):

ciento veintitrés mil cuatrocientos cincuenta y seis

El código aquí.

Género

¿Género? Si, algunos valores pueden tener género y son casos que hay que tener en cuenta. En concreto las centenas y la terminación «uno».

doscientas, trescientas, cuatrocientas, una, veintiuna, …

Decimales

Cuando hay decimales se indica con el separador: «coma», «punto», «con» y se lee el número decimal como si fuera otro número. Cumplen las mismas reglas, a excepción de que son femeninos. Si no se pone indicador de valor significa que es el número leído desde la coma.

«Con tres milésimas» = ,003

«Con tres» = ,3

Para los decimales el texto se separa en dos partes, la entera y la decimal, y se procesan por separado. La parte decimal se procesa igual que la entera. Pero al final hay que añadirle las unidades, que dependerán del número de dígitos.

0,0X décimas
0,0X centésimas
0,00X milésimas
0,00000X millonésima

Es importante tener en cuenta que los primeros números pueden ser ceros que no afectan a la forma de escribirlo, pero si al valor que se añade al final:

,3  = «tres décimas»

,03  = «tres centésimas»

,003  = «tres milésimas»

Cómo JavaScript es tan «particular» con los decimales se ha optado por usar dos enteros, uno representa la parte decimal y otro el número por el que hay que dividirla.

No sólo números

En los números no sólo hay números, hay otros símbolos a tener en cuenta. Separadores de miles, separador decimal, símbolo de menos, monedas, unidades, símbolo de %.