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
1
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:
•
•
•
•
•
Prolog
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
2
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
3
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
Prolog
<cn>::= <prep> <sn>
<prep>::= de
4
<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
sv(SV):frase(L):- append(SN,SV,L),
sn(SN),
sv(SV).
sn(SN):-
sn(SN):Prolog
append(SNA,SNB,SN),
adjetivo(SNA),
nombre(SNB).
append(SNA,SNB,SN),
determinante(SNA),
nombre(SNB).
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).
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]).
5
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
6
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
7
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|Z]
difference_append([a,b,c], [d,e],
- Z, [d,e]
R). - [ ], R- [ ]).
No.
Z = [d, e]
?- =difference_append([a,b,c]-Z,
R
[a, b, c, d, e]
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]
Yes
R = [a, b, c, d, e] - [ ]
Yes
Prolog
8
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).
Prolog
?- list_to_dl([a,b,c],Y-Z).
Y = [a, b, c|_G167]
Z = _G167 ;
No
9
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
10
<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
sv(SV-R):-
frase(S):-
verbo(SV-SV1),
atributo(SV1-R).
sn(S-S1),
sv(S1-[]).
sn(SN-R):adjetivo(SN-SN1),
nombre(SN1-R).
sn(SN-R):-
cn(SV-R):determinante(SN-SN1),
nombre(SN1-R).
Prolog
atributo(SA-R):sn(SA-SA1),
cn(SA1-R).
prep(SV-SV1),
sn(SV1-R).
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).
11
?- 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
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
sv(SV-R):verbo(SV-SV1),
atributo(SV1-R).
adjetivo([mi|X]-X).
adjetivo([mis|X]-X).
adjetivo([tu|X]-X).
adjetivo([tus|X]-X).
atributo(SA-R):sn(SA-SA1),
cn(SA1-R).
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).
cn(SV-R):prep(SV-SV1),
sn(SV1-R).
verbo([es|X]-X).
determinante([el|X]-X).
prep([de|X]-X).
12
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
adjetivo
adjetivo
adjetivo
Prolog
-->
-->
-->
-->
[mi].
[mis].
[tu].
[tus].
nombre
nombre
nombre
nombre
nombre
nombre
nombre
-->
-->
-->
-->
-->
-->
-->
[hermano].
[abuelo].
[primo].
[padres].
[tíos].
[hijo].
[padre].
verbo --> [es].
determinante --> [el].
prep --> [de].
13
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:
se traduce en:
frase --> sn, sv.
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
14
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:
se traduce en:
adjetivo --> [mi].
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:
Prolog
?- frase([mi,abuelo,es,el,padres,de,mis,padre],R).
R = []
15
Yes
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:
frase --> sn, sv.
sn --> adjetivo(N), nombre(N).
sn --> determinante(N), nombre(N).
sv --> verbo, atributo.
atributo --> sn, cn.
cn --> prep, sn.
adjetivo(sing)
adjetivo(plur)
adjetivo(sing)
adjetivo(plur)
-->
-->
-->
-->
[mi].
[mis].
[tu].
[tus].
nombre(sing)
nombre(sing)
nombre(sing)
nombre(plur)
nombre(plur)
nombre(sing)
nombre(sing)
-->
-->
-->
-->
-->
-->
-->
[hermano].
[abuelo].
[primo].
[padres].
[tíos].
[hijo].
[padre].
verbo --> [es].
determinante(sing) --> [el].
prep --> [de].
Ahora una frase como:
?- frase([mi,primo,es,el,hijo,de,mi,tíos],R).
No
Prolog
ya
no resulta aceptable
16
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
17
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
18
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).
Prolog
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
19
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
20
Descargar

Presentación en PowerPoint del tema de Introducción al