;; Text prediction using unigram and bigram techniques ;; Copyright (C) 2008 Alejandro Blanco and Manuel Gomar ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;; Algoritmos de texto predictivo ;; http://www.cs.us.es/cursos/ia2/trabajos/propuesta-2/propuesta-fjmm.html ;; Francisco Jesús Martín Mateos (fjesus@us.es) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ALUMNOS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Alejandro Blanco Escudero - DNI: 77807028Y ;; ;; Manuel Gomar Acosta - DNI: 14317632V ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ESTRUCTURAS DE DATOS ;;;;;;;;;;;;;;;;;;;;;;; ;; Rutas de los ficheros (defparameter *corpus-location* nil) (defparameter *diccionario-location* '"aprendizaje.txt") ;; Tablas hash con la información extraída de los corpus (defparameter *corpus* (make-hash-table)) (defparameter *corpus-dobles* (make-hash-table :test 'equal)) (defparameter *corpus-key* (make-hash-table)) (defparameter *teclado* nil) (defparameter *profundidad* 9) ;; Número de palabras predichas (defvar *palabras-totales* 0) ;; Número total de palabras reconocidas hasta el momento ;;Inicializa la variable teclado con los valores correspondientes (defun crea-teclado () (setf *teclado* (list (cons (codifica-palabra-ascii "'¿?¡()/!)#0,:;-") 0) (cons (codifica-palabra-ascii ". 1") 1) (cons (codifica-palabra-ascii 'aábc2) 2) (cons (codifica-palabra-ascii 'deéf3) 3) (cons (codifica-palabra-ascii 'ghií4) 4) (cons (codifica-palabra-ascii 'jkl5) 5) (cons (codifica-palabra-ascii 'mnoóñ6) 6) (cons (codifica-palabra-ascii 'pqrs7) 7) (cons (codifica-palabra-ascii 'tuúvü8) 8) (cons (codifica-palabra-ascii 'wxyz9) 9)))) ;; FUNCIONES DE MANEJO DE DATOS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Devuelve una lista de palabras que es llegar posible escribir, que empiezan por las ;; pulsaciones dadas, ordenadas por probabilidad (defun get-lista-palabras-relacionadas (numero &optional (palabra-anterior nil) (lista-numeros nil)) (let ((lista (get-lista-palabras-relacionadas-aux numero))) (subseq ;; Tomamos solo una lista de tam máximo *profundidad* (ordena-por-probabilidad (calcula-probabilidad (append lista (get-posibles-palabras ;; Palabras que puedes llegar a escribir teniendo.. palabra-anterior ;; palabra-anterior ... lista-numeros)) ;; y habiendo pulsado los números palabra-anterior)) ;; Probabilidad bipalabra 0 (min *profundidad* (length lista))))) ;; Palabras del corpus-compuesto ;; (get-lista-palabras-relacionadas 22) ;; (("bar" . 13/36324) ("acá" . 5/36324) ("can" . 1/18162) ("22" . 1/36324) ("cae" . 1/36324)) ;; Devuelve una lista de palabras relacionadas con la palabra anterior y que contengan la lista de numeros (defun get-posibles-palabras (palabra-anterior lista-numeros) (loop for palabra in (gethash palabra-anterior *corpus-dobles*) when (member ;; Sólo las que contengan la secuencia de números lista-numeros (list ;; Tiene que ser una lista para que pueda compararlo (subseq (codifica-palabra-lista (first palabra)) 0 (min (length lista-numeros) (length (first palabra))))) :test #'equal) collect palabra)) ;; (get-posibles-palabras "una" '(7 2)) ;; (("palabra" . 1) ("rama" . 1) ("rata" . 1) ("pasada" . 1) ("sanitaria" . 1) ;; ("pancita" . 1) ("panza" . 4) ("pareja" . 1) ("pata" . 1) ("pasta" . 1)) ;; Funcion auxiliar ;; Devuelve una lista de palabras relacionadas con un número (defun get-lista-palabras-relacionadas-aux (numero) (append (gethash numero *corpus*) ;; Las que se pueden escribir con esas pulsaciones (loop for x in (gethash numero *corpus-key*) ;; Las que se pueden llegar a escribir con esas pulsaciones append (get-lista-palabras-relacionadas-aux x)))) ;; (get-lista-palabras-relacionadas-aux 52) ;; (("52" . 1) ("la" . 823) ("las" . 122)) ;;Inserta una palabra en el corpus actualizando sus repeticiones (defun add-palabra (p1 &optional (p2 nil)) (let* ((palabra (sin-punto p1)) (palabra-anterior (sin-punto p2)) (numero (codifica-palabra palabra))) (setf *palabras-totales* (1+ *palabras-totales*)) (setf (gethash numero *corpus*) (add-palabra-aux palabra (gethash numero *corpus*))) (cond ((not (null palabra-anterior)) ;; Palabra doble (setf (gethash palabra-anterior *corpus-dobles*) (add-palabra-aux palabra (gethash palabra-anterior *corpus-dobles*))))) (set-key palabra))) ;; Inserta una palabra en una lista, si está le suma 1 a sus apariciones, si no está le da valor 1 (defun add-palabra-aux (palabra lista) (if (assoc palabra lista :test #'equal) ;; Se comprueba si está en la lista (loop for x in lista collect (if (equal (first x) (string palabra)) ;; Si es la buscada (cons palabra (1+ (rest x))) ;; Suma una aparición x)) ;; No lo es (cons (cons palabra 1) ;; No está en la lista lista))) ;;> (add-palabra-aux "hola" '(("hola" . 1) ("uno" . 1) ("dos" . 2) ("tres". 3))) ;; (("hola" . 2) ("uno" . 1) ("dos" . 2) ("tres" . 3)) ;; Incluye en corpus key todas las posibles palabras que se pueden llegar a escribir a partir de la dada (defun set-key (palabra1) (let ((numero (codifica-palabra palabra1))) (set-key-aux palabra1 (lista-a-numero-aux (subseq (codifica-palabra-lista palabra1) 0 (1- (length palabra1)))) numero))) (defun set-key-aux (palabra indice numero) (if (member numero (gethash indice *corpus-key*)) nil (setf (gethash indice *corpus-key*) (cons numero (gethash indice *corpus-key*))))) ;; Devuelve la probabilidad de una palabra (defun get-probabilidad (palabra &optional (palabra-anterior nil)) (if (null palabra-anterior) (/ ;; Unigram (rest (assoc palabra (gethash (codifica-palabra palabra) *corpus*) :test #' string-equal)) *palabras-totales*) (if (null (rest (assoc palabra (gethash palabra-anterior *corpus-dobles*) :test #' string-equal))) 0 ;; Caso en el que la palabra no aparece nunca detrás de palabra-anterior (/ ;; Bigram (rest (assoc palabra (gethash palabra-anterior *corpus-dobles*) :test #' string-equal)) (rest (assoc palabra-anterior (gethash (codifica-palabra palabra-anterior) *corpus*) :test #' string-equal)))))) ;; FUNCIONES PROBABILISTICAS ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Calcula la probabilidad total de cada elemento de la lista (defun calcula-probabilidad (lista &optional (palabra-anterior nil)) (loop for x in lista collect (cons (first x) (get-probabilidad (first x) palabra-anterior)))) ;; Ordena de mayor a menor una lista de (palabras . probabilidades) (defun ordena-por-probabilidad (lista) (sort lista #'(lambda (x y) (> (rest x) (rest y))))) ;; Lee el fichero que le pasan por parametro y cuenta las apariciones ;; de las palabras e inicia las probabilidades (defun entrenamiento (fichero) (let ((anterior nil) (x nil)) (with-open-file (s fichero) (do ((l (read-line s) (read-line s nil 'eof))) ((eq l 'eof) "Fin de Fichero.") ;; (format t "~&DEBUG - ~a" l) (loop for elem in (parser l) do (setf x (limpieza elem)) (cond ((< 0 (length x)) (add-palabra x anterior) (if (punto-al-final x) (setf anterior nil) (setf anterior x))))))))) ;; Incrementa el número de apariciones totales, y el de apariciones de la palabra ;; Si la palabra no estaba en el *corpus* la incluye (defun aprendizaje (palabra) (add-palabra (string-downcase palabra))) ;; Normaliza una lista de (palabras . probabilidades) (defun normaliza-lista (lista) (let* ((suma (loop for x in lista summing (rest x))) (alfa (if (= 0 suma) 1 (/ 1 suma)))) (loop for x in lista collect (cons (first x) (* alfa (rest x)))))) ;; Devuelve las palabras que se pueden llegar a escribir con esas ;; pulsaciones de teclas, ordenadas por probabilidad (defun prediccion (teclas &optional (palabra-anterior nil)) (remove-duplicates (get-lista-palabras-relacionadas (lista-a-numero-aux teclas) palabra-anterior teclas) :test #'equal)) ;; (funcion-de-evaluacion "254674866 33 83986 7733428486") (defun funcion-de-evaluacion (cadena) (parser-inversa (loop for x in (parser cadena) collect (first (first (get-lista-palabras-relacionadas (string-to-integer x))))))) ;; (funcion-de-aprendizaje "algoritmo de texto predictivo") (defun funcion-de-aprendizaje (cadena) (loop for x in (parser cadena) do (aprendizaje x))) ;; FUNCIONES DE CODIFICACION ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;Función que pasa de una cadena a una lista de cadenas (palabras) (defun parser (cadena) (loop for x in (let ((lista (append (loop for x across cadena collect x) (list (character " ")))) ;; Insertamos espacio al final (ind -1)) ;; Indice (loop for i from 0 to (length lista) when (string= (nth i lista) '#\Space) ;; Si hay un espacio collect (string-downcase (subseq cadena (1+ ind) (setf ind i))))) when (< 0 (length x)) collect x)) ;;> (parser "hola a todos soy una cadena ") ;; ("hola" "a" "todos" "soy" "una" "cadena" "") ;; Función inversa a parser (defun parser-inversa (lista) (apply #'string-concat (reverse (rest (reverse (loop for x in lista append (list (string x) " "))))))) ;;> (parser-inversa '("hola" "a" "" "" "todos" "soy" "una" "" "" "cadena" "")) ;; "hola a todos soy una cadena " ;;Pasa de un string "2222" a un número 2222 (defun string-to-integer (cadena) (lista-a-numero-aux (loop for x across cadena collect (cond ((equal x '#\0) 0) ((equal x '#\1) 1) ((equal x '#\2) 2) ((equal x '#\3) 3) ((equal x '#\4) 4) ((equal x '#\5) 5) ((equal x '#\6) 6) ((equal x '#\7) 7) ((equal x '#\8) 8) (t 9))))) ;;> (string-to-integer "22222") ;; 22222 ;; Codifica la palabra a una lista de codigos ascii (defun codifica-palabra-ascii (palabra) (loop for x across (string-downcase palabra) collect (char-code (character x)))) ;;> (codifica-palabra-ascii 'hola) ;; (104 111 108 97) ;; Codifica una palabra a un número de teclado (defun codifica-palabra (palabra) (lista-a-numero-aux (codifica-palabra-lista (string-downcase palabra)))) ;;> (codifica-palabra 'hola) ;; 4652 ;;Codifica una palabra a una lista de números del teclado (defun codifica-palabra-lista (palabra) (loop for x across (string-downcase palabra) when (assoc (char-code x) *teclado* :test #'member) collect (rest (assoc (char-code x) *teclado* :test #'member)))) ;;> (codifica-palabra-lista 'hola) ;; (4 6 5 2) ;; Pasa una lista de números '(1 2 3 4 5) a un literal '12345 (defun lista-a-numero-aux (lista) (let ((tam (1- (length lista))) (l (reverse lista))) (loop for i from 0 to tam summing (* (expt 10 i) (nth i l))))) ;;> (lista-a-numero-aux '(1 2 3 4 5)) ;; 12345 ;; Devuelve cierto si el último caracter de la palabra es un '.' (defun punto-al-final (palabra) (equal (aref palabra (1- (length palabra))) '#\.)) ;; Le quita el punto del final a la palabra, si es que lo tenía (defun sin-punto (palabra) (if (null palabra) nil (if (punto-al-final palabra) (subseq palabra 0 (1- (length palabra))) palabra))) ;; Limpia una cadena de los siguintes símbolos especiales "'¿?¡()/!#,:;- ;; Los de cierre ? y ! se convierten a puntos . (defun limpieza (palabra) (lista-char-a-string (loop for x across (string-downcase palabra) collect (cond ((or (equal x '#\') (equal x '#\¿) (equal x '#\¡) (equal x '#\() (equal x '#\)) (equal x '#\/) (equal x '#\#) (equal x '#\,) (equal x '#\:) (equal x '#\;) (equal x '#\-) (equal x '#\")) nil) ((or (equal x '#\?) (equal x '#\!)) '#\.) (t x))))) ;; Pasa una lista de char a un string, saltándose aquellos que sean nil (defun lista-char-a-string (l) (let ((cadena '"")) (loop for x in l do (if (null x) nil (setf cadena (string-concat cadena (string x))))) cadena)) ;; FUNCIONES DE PRESENTACION ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Lanza el programa mostrando los resultados por pantalla (defun inicio (&optional (canal t)) (compile-file "principal.lsp") (load "principal") (format canal "~%~%~%") (crea-teclado) (carga-datos canal) (main canal)) ;; Carga los datos necesarios para ejecutar las funciones (defun carga-datos (canal) (configuracion canal) (format canal "~&~%Carga del diccionario~%~%") (entrenamiento *diccionario-location*)) ;; Opciones del programa (defun configuracion (canal) (escoge-corpus canal) (format canal "~&~%Número de palabras predichas (por ejemplo 9): ") (setf *profundidad* (read))) ;; Permite escoger los corpus a utilizar (defun escoge-corpus (canal) (let ((lista (directory 'corpus/*)) (opcion 1) (terminado nil)) (loop while (not terminado) do (format canal "~&Escoge el corpus que quieres procesar: ") (loop for x in lista for i from 1 to (length lista) do (format canal "~&~a.- ~a" i x)) (format canal "~&~a.- Continuar" (1+ (length lista))) (format canal "~&Corpus a utilizar: ") (setf opcion (read)) (cond ((= opcion (1+ (length lista))) (setf terminado t)) (t (setf *corpus-location* (nth (1- opcion) lista)) (format canal "~&Proceso de entrenamiento~%~%") (entrenamiento *corpus-location*)))))) ;; Bucle principal del algoritmo (defun main (canal) (let ((terminado nil) (teclas '()) (tecla nil) (pred nil) (palabra nil) (frase '()) (palabra-anterior nil) (indice 0)) (loop while (not terminado) do (escribe-teclado canal) (format canal "~&~%Opciones:~%-Pulse un numero del teclado") (format canal "~%-Pulse la letra e para un espacio en blanco") (format canal "~%-Pulse la letra b para borrar la ultima pulsacion") (format canal "~%-Pulse la letra o para escoger otra palabra predicha") (format canal "~%-Pulse la letra n para incluir una palabra nueva") (format canal "~%-Pulse la letra f para terminar una frase") (format canal "~%-Pulse la letra q para salir") (format canal "~%~%Su eleccion: ") (setf tecla (read)) (cond ((eq tecla 'q) ;; ---------------------------------------------------- Salir (setf terminado t)) ((eq tecla 'e) ;; ---------------------------------------------------- Espacio en blanco (aprendizaje palabra) (setf teclas '()) (setf palabra-anterior palabra) ;; Palabra anterior (setf frase (append frase (list palabra))) (print-prediccion canal teclas palabra pred frase)) ((eq tecla 'b) ;; ---------------------------------------------------- Borrar ultima pulsación (if (null teclas) (format canal "~&~%No hay nada que borrar") (setf teclas (reverse (rest (reverse teclas))))) (setf indice 0) (setf pred (prediccion teclas palabra-anterior)) (setf palabra (first (nth indice pred))) (print-prediccion canal teclas palabra pred frase)) ((eq tecla 'n) ;; ---------------------------------------------------- Nueva palabra (format canal "~&~%Escriba la palabra: ") (setf palabra (string-downcase (read))) (aprendizaje palabra) (setf teclas '()) (setf frase (append frase (list palabra))) (print-prediccion canal teclas palabra pred frase)) ((eq tecla 'f) ;; ---------------------------------------------------- Finalizar frase (with-open-file (fich *diccionario-location* :direction :output :if-exists :append) (write-line (parser-inversa frase) fich)) (setf palabra-anterior nil) (setf teclas '()) (setf tecla nil) (setf palabra nil) (setf frase '()) (setf indice 0)) ((and (eq tecla 'o) (not (null pred)) (not (null palabra))) ;; ------- Siguiente palabra (format canal "~&~%Introduzca el indice de la palabra deseada: ") (setf indice (read)) (setf palabra (first (nth indice pred))) (print-prediccion canal teclas palabra pred frase)) ((member tecla '(1 2 3 4 5 6 7 8 9)) ;; ------------------------------ Pulsar tecla (setf teclas (append teclas (list tecla))) (setf indice 0) (setf pred (prediccion teclas palabra-anterior)) (setf palabra (first (nth indice pred))) (print-prediccion canal teclas palabra pred frase)) (t (format canal "~&~%Opcion invalida. Escoja otra vez.~%")))))) ;; Función que muestra una predicción de manera amigable (defun print-prediccion (canal teclas palabra pred frase) (format canal "~&~%Palabra predicha: ~a~%" palabra) (format canal "~&Palabras posibles: ~%") (print-prediccion-aux canal pred) (format canal "~&Frase hasta ahora: ~a~%" frase) (format canal "~&Teclas pulsadas: ~a~%~%" teclas)) ;; Función auxiliar (defun print-prediccion-aux (canal pred) (let ((prednorm (normaliza-lista pred))) (loop for i from 0 to (length prednorm) when (= 0 (mod i 3)) do (format canal "~&") (print-palabra canal i (nth i prednorm)) (print-palabra canal (+ 1 i) (nth (+ 1 i) prednorm)) (print-palabra canal (+ 2 i) (nth (+ 2 i) prednorm))))) (defun print-palabra (canal numero palabra) (if (and (not (null palabra)) (listp palabra)) (format canal "~a...'~a'(~,3F)~t~t~t" numero (first palabra) (rest palabra)))) ;; Muestra un teclado por <> (defun escribe-teclado (canal) (escribe-linea canal) (format canal "~&+ 1 + 2 + 3 +") (format canal "~&+ . + ABC + DEF +") (escribe-linea canal) (format canal "~&+ 4 + 5 + 6 +") (format canal "~&+ GHI + JKL + MNO +") (escribe-linea canal) (format canal "~&+ 7 + 8 + 9 +") (format canal "~&+ PQRS+ TUV + WXYZ+") (escribe-linea canal) (format canal "~&+ * + 0 + # +") (format canal "~&+ + + +") (escribe-linea canal)) ;; Función auxiliar (defun escribe-linea (canal) (format canal "~&+-----+-----+-----+"))