210 likes | 405 Views
Introdución al procesamiento del lenguaje natural en PROLOG. Jorge Cabrera Gámez Departamento de Informática y Sistemas Universidad de Las Palmas de Gran Canaria. Procesamiento del lenguaje natural en Prolog.
E N D
Introdución al procesamiento del lenguaje natural en PROLOG Jorge Cabrera Gámez Departamento de Informática y Sistemas Universidad de Las Palmas de Gran Canaria Prolog
Procesamiento del lenguaje natural en Prolog • Prolog ofrece algunas facilidades para definir analizadores gramaticales y, en general, para diseñar sistemas orientados al procesamiento del lenguaje natural. • Supongamos que necesitamos definir un programa que sea capaz de aceptar frases como: • Mi abuelo come papaya. • El coche rojo es muy veloz. • Tu hermano es el hijo de tus padres • Tu abuelo es el padre de tus padres • Mi primo es el hijo de mis tíos Prolog
Procesamiento del lenguaje natural en Prolog • Tu hermano es el hijo de tus padres • Tu abuelo es el padre de tus padres • Mi primo es el hijo de mis tíos • Estas frases se ajustan a la siguiente gramática BNF: • <frase>::= <sn> <sv> • <sn>::= <adjetivo> <nombre> | <determinante> <nombre> • <sv>::= <verbo> <atributo> • <atributo>::= <sn> <cn> • <adjetivo>::= tu | mi | mis | tus • <nombre>::= hermano | abuelo | primo | padres | tíos | hijo | padre • <verbo>::= es • <determinante>::= el • <cn>::= <prep> <sn> • <prep>::= de Prolog
La idea es escribir un programa que acepte una frase si se adecúa a las reglas de la gramática o la rechace en caso contrario. La frase en cuestión se presentará como una lista de palabras, p.e.: ?- frase([mi,primo,es,el,hijo,de,mis,tíos]). Una forma de implementar estas reglas gramaticales es emplear una estrategia de “generación_y_test” como muestra el siguiente ejemplo: frase(L):- append(SN,SV,L), sn(SN), sv(SV). <frase>::= <sn> <sv> <sn>::= <adjetivo> <nombre> | <determinante> <nombre> <sv>::= <verbo> <atributo> <atributo>::= <sn> <cn> <adjetivo>::= tu | mi | mis | tus <nombre>::= hermano | abuelo | primo | padres | tíos | hijo | padre <verbo>::= es <determinante>::= el <cn>::= <prep> <sn> <prep>::= de Prolog
<frase>::= <sn> <sv> <sn>::= <adjetivo> <nombre> | <determinante> <nombre> <sv>::= <verbo> <atributo> <atributo>::= <sn> <cn> <adjetivo>::= tu | mi | mis | tus <nombre>::= hermano | abuelo | primo | padres | tíos | hijo | padre <verbo>::= es <determinante>::= el <cn>::= <prep> <sn> <prep>::= de adjetivo([mi]). adjetivo([mis]). adjetivo([tu]). adjetivo([tus]). nombre([hermano]). nombre([abuelo]). nombre([primo]). nombre([padres]). nombre([tíos]). nombre([hijo]). nombre([padre]). verbo([es]). determinante([el]). prep([de]). sv(SV):- append(SVA,SVB,SV), verbo(SVA), atributo(SVB). atributo(SA):- append(SN,CN,SA), sn(SN), cn(CN). cn(SV):- append(P,SN,SV), prep(P), sn(SN). frase(L):- append(SN,SV,L), sn(SN), sv(SV). sn(SN):- append(SNA,SNB,SN), adjetivo(SNA), nombre(SNB). sn(SN):- append(SNA,SNB,SN), determinante(SNA), nombre(SNB). Prolog
Listas “diferencia” Se denominan “listas diferencia” (difference lists) a una forma de representar listas en Prolog que - como técnica de programación - puede provocar un notable incremento de eficiencia. Ejemplo: Supongamos que deseamos diseñar un procedimiento que nos permita añadir un elemento a una lista por la cabeza. add_to_head(X,Ys,[X|Ys]). Esto es fácil. Supongamos ahora que deseamos diseñar el procedimiento complementario add_to_back. Prolog
Ejemplo (cont): Realmente no es muy difícil ... add_to_back(X, [ ], [X]). add_to_back(X, [Y|Ys], [Y|Zs]):- add_to_back(X,Ys,Zs). Sin embargo, es terriblemente ineficiente por motivos evidentes. Ejemplo: Algo similar ocurre con la definición estándar de append/3: append([ ], Ys, Ys). append([X|Xs], Ys, [X|Zs]):- append(Xs,Ys,Zs). Prolog
Las listas diferencia permiten manipular listas de forma mucho más eficiente definiendo “patrones de listas”. Por ejemplo: difference_append (A-Z, Z-B, A-B). ?- difference_append([a,b,c], [d,e], R). No. ?- difference_append([a,b,c]-Z, Z-[d,e], R). Z = _G379 R = [a, b, c]-[d, e] Yes ?- difference_append([a,b,c|Z] - Z, [d,e] - [ ], R- [ ]). Z = [d, e] R = [a, b, c, d, e] Yes ?- difference_append([a,b,c|Z] - Z, [d,e] - [ ], R). Z = [d, e] R = [a, b, c, d, e] - [ ] Yes Prolog
Las siguientes definiciones transforman una lista diferencia en una lista “normal”, pero no a la inversa dl_to_list([ ] - _, [ ]) :- !. dl_to_list([X|Y] - Z, [X|W]) :- dl_to_list(Y - Z, W). ?- dl_to_list([1,2,3|X]-X,L). X = [ ] L = [1,2,3]; No Recíprocamente, list_to_dl transforma una lista “normal” en una lista diferencia, pero no a la inversa list_to_dl([], X - X). list_to_dl([X|W], [X|Y] - Z) :- list_to_dl(W, Y - Z). ?- list_to_dl([a,b,c],Y-Z). Y = [a, b, c|_G167] Z = _G167 ; No Prolog
Listas Diferencia El problema con esta estrategia es que es terriblemente ineficaz como puede comprobarse fácilmente realizando una traza de la anterior definición de frase/1. La estrategia más eficiente es evitar la etapa de generación y pasar la lista completa a los predicados que implementan las reglas gramaticales. Éstos identificarán los correspondientes elementos gramaticales procesando secuencialmente los elementos de la lista de izquierda a derecha, devolviendo el resto de la lista. Para hacer esto podemos emplear listas diferencia como se ilustra en la siguiente versión del analizador gramatical del ejemplo anterior. Prolog
<frase>::= <sn> <sv> <sn>::= <adjetivo> <nombre> | <determinante> <nombre> <sv>::= <verbo> <atributo> <atributo>::= <sn> <cn> <adjetivo>::= tu | mi | mis | tus <nombre>::= hermano | abuelo | primo | padres | tíos | hijo | padre <verbo>::= es <determinante>::= el <cn>::= <prep> <sn> <prep>::= de adjetivo([mi|X]-X). adjetivo([mis|X]-X). adjetivo([tu|X]-X). adjetivo([tus|X]-X). nombre([hermano|X]-X). nombre([abuelo|X]-X). nombre([primo|X]-X). nombre([padres|X]-X). nombre([tíos|X]-X). nombre([hijo|X]-X). nombre([padre|X]-X). verbo([es|X]-X). determinante([el|X]-X). prep([de|X]-X). sv(SV-R):- verbo(SV-SV1), atributo(SV1-R). atributo(SA-R):- sn(SA-SA1), cn(SA1-R). cn(SV-R):- prep(SV-SV1), sn(SV1-R). frase(S):- sn(S-S1), sv(S1-[]). sn(SN-R):- adjetivo(SN-SN1), nombre(SN1-R). sn(SN-R):- determinante(SN-SN1), nombre(SN1-R). Prolog
?- nombre([hijo,de,mis,tíos] - X). X = [de, mis, tíos] Yes ?- sn([mi,abuelo,es,el,padre,de,mi,padre] - X). X = [es, el, padre, de, mi, padre] Yes adjetivo([mi|X]-X). adjetivo([mis|X]-X). adjetivo([tu|X]-X). adjetivo([tus|X]-X). nombre([hermano|X]-X). nombre([abuelo|X]-X). nombre([primo|X]-X). nombre([padres|X]-X). nombre([tíos|X]-X). nombre([hijo|X]-X). nombre([padre|X]-X). verbo([es|X]-X). determinante([el|X]-X). prep([de|X]-X). sv(SV-R):- verbo(SV-SV1), atributo(SV1-R). atributo(SA-R):- sn(SA-SA1), cn(SA1-R). cn(SV-R):- prep(SV-SV1), sn(SV1-R). frase(S):- sn(S-S1), sv(S1-[ ]). sn(SN-R):- adjetivo(SN-SN1), nombre(SN1-R). sn(SN-R):- determinante(SN-SN1), nombre(SN1-R). Prolog
Gramática Definida por Cláusulas La mayoría de las implementaciones de Prolog incorporan la posibilidad de definir gramáticas mediante una sintaxis especial que oculta la presencia de las listas diferencia. A esta sintaxis se le conoce como gramática definida por cláusulas (Definite Clause Grammar, DCG). frase --> sn, sv. sn --> adjetivo, nombre. sn --> determinante, nombre. sv --> verbo, atributo. atributo --> sn, cn. cn --> prep, sn. adjetivo --> [mi]. adjetivo --> [mis]. adjetivo --> [tu]. adjetivo --> [tus]. nombre --> [hermano]. nombre --> [abuelo]. nombre --> [primo]. nombre --> [padres]. nombre --> [tíos]. nombre --> [hijo]. nombre --> [padre]. verbo --> [es]. determinante --> [el]. prep --> [de]. Prolog
Gramática de Cláusulas Definidas • Las cláusulas gramaticales así definidas se analizan y “traducen” en cláusulas Prolog que emplean listas diferencias. Por ejemplo, la primera de las reglas: • frase --> sn, sv. • se traduce en: • frase(A, B) :- • sn(A, C), • sv(C, B). • Así que para analizar una frase, hemos de invocar frase/2 con dos argumentos: • ?- frase([mi,abuelo,es,el,padre,de,mi,padre],R). • R = [] • Yes • Evidentemente , el segundo argumento “recogerá” el resto “inaceptado” de la frase cuando éste exista. Prolog
Gramática de Cláusulas Definidas • Las cláusulas gramaticales que recogen los símbolos terminales, el vocabulario, se traducen también en listas diferencias. Por ejemplo: • adjetivo --> [mi]. • se traduce en: • adjetivo([mi|A],A). • La gramática que hemos definido presenta algunas deficiencias como la falta de concordancia entre el número del adjetivo y el nombre. Por ejemplo, “mi padres” resultaría aceptable como sintagma nominal (sn): • ?- sn([mi,padres], R). • R = [] • Yes • Por ello una frase como la siguiente sería aceptable: • ?- frase([mi,abuelo,es,el,padres,de,mis,padre],R). • R = [] • Yes Prolog
Uso de Variables en DCGs Para resolver este problema podemos emplear argumentos en las cláusulas de la gramática como se muestra a continuación: nombre(sing) --> [hermano]. nombre(sing) --> [abuelo]. nombre(sing) --> [primo]. nombre(plur) --> [padres]. nombre(plur) --> [tíos]. nombre(sing) --> [hijo]. nombre(sing) --> [padre]. verbo --> [es]. determinante(sing) --> [el]. prep --> [de]. frase --> sn, sv. sn --> adjetivo(N), nombre(N). sn --> determinante(N), nombre(N). sv --> verbo, atributo. atributo --> sn, cn. cn --> prep, sn. adjetivo(sing) --> [mi]. adjetivo(plur) --> [mis]. adjetivo(sing) --> [tu]. adjetivo(plur) --> [tus]. • Ahora una frase como: • ?- frase([mi,primo,es,el,hijo,de,mi,tíos],R). • No • ya no resulta aceptable Prolog
Uso de Variables en DCGs El uso de variables no está restringido al cuerpo de las reglas. Así la versión de la gramática que se muestra en la siguiente diapositiva usa variables para devolver un análisis sintáctico (y eventualmente morfológico) de la frase. Por ejemplo (ligeramente retocado): ?- frase(S,[mi,primo,es,el,hijo,de,mis,tíos],[]). S = análisis( sn(adj(mi), nom(primo)), sv(v(es), atrib(sn(det(el), nom(hijo)), cn(prep(de), sn(adj(mis), nom(tíos)))))) Yes Prolog
frase(análisis(S,V)) --> sn(S), sv(V). sn(sn(A,B)) --> adjetivo(A,N), nombre(B,N). sn(sn(A,B)) --> determinante(A,N), nombre(B,N). sv(sv(V,A)) --> verbo(V), atributo(A). atributo(atrib(S,C)) --> sn(S), cn(C). cn(cn(P,S)) --> prep(P), sn(S). adjetivo(adj(mi),sing) --> [mi]. adjetivo(adj(mis),plur) --> [mis]. adjetivo(adj(tu),sing) --> [tu]. adjetivo(adj(tus),plur) --> [tus]. nombre(nom(hermano),sing) --> [hermano]. nombre(nom(abuelo),sing) --> [abuelo]. nombre(nom(primo),sing) --> [primo]. nombre(nom(padres),plur) --> [padres]. nombre(nom(tíos),plur) --> [tíos]. nombre(nom(hijo),sing) --> [hijo]. nombre(nom(padre),sing) --> [padre]. verbo(v(es)) --> [es]. determinante(det(el),sing) --> [el]. prep(prep(de)) --> [de]. Prolog
Es posible incluir cláusulas “prolog” en la definición de las cláusulas gramaticales. Las cláusulas “prolog” deben encerrarse entre llaves { }, como se muestra en el siguiente ejemplo, donde se han agrupado las definiciones de los adjetivos en la gramática que se ha venido usando como ejemplo (exactamente lo mismo puede hacerse con los nombres): adjetivo(adj(X),sing) --> [X],{ member(X,[mi,tu]) }. adjetivo(adj(X),plural) --> [X], { concat_atom([Y,s],X), adjetivo(adj(Y),sing,[Y],[])}. ?- listing(adjetivo). adjetivo(adj(A), sing, B, C) :- 'C'(B, A, D), member(A, [mi, tu]), C=D. adjetivo(adj(A), plural, B, C) :- 'C'(B, A, D), concat_atom([E, s], A), adjetivo(adj(E), sing, [E], []), C=D. Yes Prolog
frase(análisis(S,V)) --> sn(S), sv(V). sn(sn(A,B)) --> adjetivo(A,N), nombre(B,N). sn(sn(A,B)) --> determinante(A,N), nombre(B,N). sv(sv(V,A)) --> verbo(V), atributo(A). atributo(atrib(S,C)) --> sn(S), cn(C). cn(cn(P,S)) --> prep(P), sn(S). adjetivo(adj(X),sing) --> [X],{ member(X,[mi,tu]) }. adjetivo(adj(X),plural) --> [X], { concat_atom([Y,s],X), adjetivo(adj(Y),sing,[Y],[])}. nombre(nom(X),sing) --> [X], { member(X,[abuelo,hermano,padre, primo,tío,hijo]) }. nombre(nom(X),plural) --> [X], { concat_atom([Y,s],X), nombre(nom(Y),sing,[Y],[])}. verbo(v(es)) --> [es]. determinante(det(el),sing) --> [el]. prep(prep(de)) --> [de]. Prolog