41
César Antonio Aguilar Facultad de Lenguas y Letras 11/04/2013 Curso de procesamiento del lenguaje natural [email protected]

Curso de procesamiento del lenguaje natural - César Antonio …cesaraguilar.weebly.com/uploads/2/7/7/5/2775690/pln_uc... · 2013-04-11 · En esta sesión, vamos a ver algunos

Embed Size (px)

Citation preview

César Antonio Aguilar

Facultad de Lenguas y Letras

11/04/2013

Curso de procesamiento del

lenguaje natural

[email protected]

Explorando NLTK (1)

En esta sesión, vamos a ver algunos de los recursos básicos que tiene NLTK, así como avanzar en cuestiones de programación relacionadas con el uso de expresiones regulares (Regexp).

A grandes rasgos, NLTK es una colección de herramientas que nos permite hacer varias tareas: desde procesar uno o varios documentos para fines sencillos (contar palabras, agrupar tokens, observar distribuciones de frecuencias, etc.), hasta cuestiones como análisis sintáctico, desambigüación de frases y/u oraciones y tareas de extracción de información.

Nota: aquí tienen la liga a un video en donde Steven Bird, Ewan Klein y Edward Loper,

los inventores de NLTK, platican durante una hora

(aprox.) qué es NLTK y qué se puede hacer con él.

www.youtube.com/watch?v=keXW_5-llD0

Explorando NLTK (2)

NLTK Book (1)

Hagamos una pequeña exploración por nuestra cuenta: de momento concentrémonos en dos módulos de NLTK: uno denominado NLTK Book, y otro que colecta todas las demos que varios desarrolladores han elaborado e integrado a NLTK.

Empecemos por el módulo Book. Escriban en el intérprete de Python la siguiente instrucción:

from nltk.book import *

Si aplicamos esta instrucción, nos van a salir 9 archivos denominados texts (1-9), los cuales son documentos tales como novelas, guiones de cine, fragmentos de prensa escrita, etc.:

NLTK Book (2)

Con estos textos, podemos hacer algunas tareas. P.e., usemos el comando concordance, realizamos una búsqueda sencilla de concordancias:

NLTK Book (3)

Otra tarea sencilla que podemos realizar es reconocer qué palabras muestran un rango similar de aparición dentro de los contextos de nuestras concordancias:

NLTK Book (4)

P.e., usando la palabra “Camelot”, y combinándolas con “fight”, “and” y “sir”, obtenemos algunas palabras asociadas que aparecen en el contexto de las concordancias.

Podemos acotar el espacio de búsqueda para estos contextos, usando pares de palabras. Para ello, ocupamos la instrucción: common_contexts:

NLTK Book (5)

Este módulo ya nos permite hacer algunos análisis de frecuencias de uso entre palabras. P. e., con el comando dispersion_plot, podemos crear un grafo de dispersión. Veamos:

NLTK Book (6)

Aprovechemos un poco estas herramientas para ver si podemos obtener algo con textos nuevos. De momento, trabajaremos con documentos en inglés.

Para importar un archivo a nuestro intérprete de Python, requerimos utilizar la instrucción open, y escribir la ruta de acceso a nuestro archivo del siguiente modo:

Cadena_Ariel01 = open(‘C:/Python26/Prueba_UAQ/ariel01.txt', 'rU').read()

Importando archivos (1)

Con la instrucción anterior, básicamente lo que hicimos fue “cargar” un archivo, el cual fue transformado en una cadena de caracteres tras el uso de comando anterior.

Esta cadena cuenta con una propiedad: que está “cruda” (raw), indicado por la letra r, ya que la hemos generado a partir de nuestro archivo. Por otro lado, señalamos que esta cadena también puede tener caracteres Unicode, representados con la letra U.

Así, lo que obtenemos es una cadena. Ahora, lo que requerimos en convertirla en un conjunto de palabras. Para eso, ocupamos la siguiente instrucción:

Text_Ariel = re.split(r'_espacio en blanco_', Cadena_Ariel01)

Importando archivos (2)

¿Qué fue lo que hicimos con la instrucción

anterior? Convertir nuestra cadena en una

lista (o en un conjunto). Como objetos, las listas

tienen permiten empezar a hacer algunos

tratamiento particulares al PLN, como por

ejemplo la asignación de etiquetas

morfosintácticas. Por lo pronto, lo que tenemos

es un grupo de diferentes objetos:

Importando archivos (3)

La instrucción anterior opera del siguiente modo:

1. Al inicio de nuestro código, debemos importar un módulo llamado re, el cual nos sirve para realizar operaciones con expresiones regulares.

2. Una vez importando este módulo, vamos a ocupar la función split, que literalmente nos ayuda a rebanar los elementos que conforman nuestra cadena, de tal forma que obtenemos un listado de objetos.

3. Los objetos que conforman nuestra lista son palabras, números y signos de puntuación. Obviamente, no estamos considerando aquí tablas, gráficos, esquemas, etc.

Importando archivos (4)

Así, nuestra instrucción se traduce como: “a partir del módulo Regular Expresions, aplica la función split, en concreto de un texto crudo (raw, r), secciona aquellos objetos que tengan el atributo word. Aplica esta función al objeto Cadena_Ariel01”. Veamos el resultado:

Hagamos una mejor obtención de palabras. La variable W, que significa word, ya nos permite segmentar únicamente aquellas cosas que podemos ver como formas de palabras:

re.split(r'\W+', Cadena_Ariel01)

Importando archivos (5)

Importando archivos (6)

Ahora, ¿qué podemos hacer con este procesamiento? Supongamos que una tarea tan tediosa como “limpiar un corpus”, podemos automatizarla usando expresiones regulares, incluso desde la importación de nuestro archivo. Veamos la siguiente lista de expresiones regulares:

Importando archivos (7)

Lo que estamos pidiendo con estas combinaciones de símbolos es:

1. Fijar de forma detallada el contenido de cualquier expresiones regulares. Esto lo representamos con el signo ? (“cualquier caracter).

2. Agrupa e integra abreviaturas como si fueran palabras.

3. Palabras que tengan puedan ser divisibles (p. e., tren-metro-trans-siberiano).

4. Números, porcentajes, valores monetarios, etc.

5. Signos de puntuación: se consideran como objetos separados de palabras.

Ahora, ocupemos el siguiente comando:

nltk.regexp_tokenize(Cadena_Ariel01, pattern)

Importando archivos (8)

¿Qué obtenemos? Un listado de palabras bastante organizado y claro, que nos permite separar mejor palabras de otra clase de objetos:

Importando archivos (9)

Tokenización (1)

len(Tokens01)

Tokens01 = nltk.word_tokenize(Cadena_Ariel01)

Usando NLTK, contamos ya con varias instrucciones que ejecutan procesos de manera automática. Veamos el caso de la tokenización. Escriban lo siguiente:

¿Qué obtuvimos? Podemos deducirlo usando aplicando las siguientes funciones?

¿Cuántos objetos tenemos en nuestro conjunto?

type(Tokens01)

¿Qué tipo de objeto tenemos?

Veamos nuestros resultados:

Tokenización (2)

Ahora, una vez que hemos tokenizado nuestra cadena de caracteres (y que al final hemos convertido en una lista de tokens), podemos transformarla en un texto legible para el buscador de concordancias de NLTK, usando la siguiente instrucción:

Tokenización (3)

El resultado es:

Tokenización (4)

Concordancias con nuevos textos (1)

Una vez que hemos generado nuestro texto, apliquemos algunas herramientas conocidas. P.e., ¿podemos generar una lista de posibles colocaciones? Veamos:

Ahora, busquemos algunas concordancias basadas en este listado de colocaciones. ¿Qué les parece la palabra theory?:

Concordancias con nuevos textos (2)

Veamos el resultado en la siguiente lámina:

Finalmente, tratemos de generar un gráfico de dispersión. Consideremos estas palabras:

Concordancias con nuevos textos (3)

Concordancias con nuevos textos (4)

Ejercicio (1)

Ahora, ustedes:

1. Trabajen con el siguiente archivo: Turing_Test01.txt, que es un fragmento de la definición del test de Turing tomado de la Stanford Enciclopedia of Philosophy (http://plato.stanford.edu/entries/turing-test/).

2. Sigan el proceso que hemos visto hasta ahora, de tal forma que transformen este archivo en una cadena, luego en una lista de palabras, inmediatamente en una lista de tokens, y al final en un texto.

3. Apliquen las herramientas para búsqueda de colocaciones y concordancias de NLTK, y obtengan algunos resultados.

4. Finalmente, generen un gráfico de dispersión con algunas de las palabras que hayan obtenido a través de su lista de colocaciones.

Para convertir nuestros archivos en cadenas, listas y textos, empleamos algunos caracteres y métodos propios del uso de expresiones regulares (o Regexp). Pregunta: ¿qué son las expresiones regulares?:

Una expresión regular es un tipo de patrón o estructura que describe un conjunto de cadenas sin enumerar sus elementos.

Es “regular porque su codificación tiende a ser repetitiva (de hecho, se pueden considerar una especie de “rutina” para comunicar cierta información).

Expresiones regulares (1)

Esto equivale a decir algo: “una cadena formada por los caracteres 'H', las opciones 'a', o 'ä', o 'ae', 'n', 'd', 'e' y 'l'”. Recuerden: no estamos trabajando con una palabra en sí, sino con una secuencia de signos.

H(a|ä|ae)ndel

¿Y para qué queremos usar expresiones regulares? Veamos el siguiente ejemplo: supongamos que queremos hacer búsquedas con el nombre Händel, y tenemos que lidiar con dos variantes de éste: Handel y Haendel.

Para resolver este problema, podríamos agrupar estas tres variantes bajo una estructura regular del tipo:

Expresiones regulares (2)

Las expresiones regulares son

bastante útiles para muchísimas

cosas: hacer búsquedas de

palabras, tareas de procesamiento

de textos, creación de gramáticas

formales, generación de

textos, etc.

Expresiones regulares (3)

Expresiones regulares (4)

http://bwananet.iula.upf.edu/

Python, como muchos lenguajes de programación, cuenta con métodos y colecciones de expresiones regulares. En este caso, se agrupan en el módulo re. Veamos algunas de las más conocidas:

Negaciones

Operaciones con conjuntos

Expresiones regulares (5)

Operaciones con cadenas

Expresiones regulares (6)

Usando métodos con Regexp (1)

Vamos a realizar algunas operaciones empleando expresiones regulares. Veamos:

2. Una vez que hayan creado su cadena, vamos a aplicar los siguientes procesos:

2.1. capitalize() : devuelve una copia de la cadena con el primer carácter en mayúscula. Escriban entonces: “Nombre_de_su_cadena”.capitalize()

1. Escribamos la siguiente cadena: “Python es un lenguaje interpretado, orientado a objetos, para fines generales. Python resulta muy adecuado para manipular archivos de texto”.

2.2. count (sub[, start[, end]]) : devuelve cuántas veces aparece subcadena en la cadena principal o S [start:end]. Los argumentos opcionales start y end se interpretan según la notación de corte. Escriban: “Nombre_de_su_cadena”.count(‘de’).

2.3. replace (old, new[, maxsplit]) : devuelve una copia de la cadena en la que se han sustituido todas las apariciones de old por new. Si se proporciona el argumento opcional maxsplit, sólo se sustituyen las primeras maxsplit apariciones. Escriban: “Nombre_de_su_cadena”.replace(‘de’,’del’)

2.4. find (sub[, start[, end]]) : devuelve el menor índice de la cadena para el que sub se encuentre, de tal modo que sub quede contenido en el rango [start, end). Los argumentos opcionales start y end se interpretan según la notación de corte. Devuelve -1 si no se halla sub. Escriban: “Nombre_de_su_cadena”.find(‘de’).

Usando métodos con Regexp (2)

2.5. split ([sep [,maxsplit]]) : devuelve una lista de las palabras de la cadena, usando sep como delimitador de palabras. Si se indica maxsplit, se devolverán como mucho maxsplit valores (el último elemento contendrá el resto de la cadena). Si no se especifica sep o es None, cualquier espacio en blanco sirve de separador.

Escriban “Nombre_de_su_cadena”.split(), para generar un lista dividiendo la cadena en palabras (delimitadas por espacio).

Otro ejemplo: escriban “Nombre_de_su_cadena”.split(‘.’), para dividir la cadena en una lista de oraciones delimitadas por un punto.

Usando métodos con Regexp (3)

2.6. endswith (suffix[, start[, end]]) : devuelve verdadero si la cadena finaliza con el sufijo suffix especificado, en caso contrario es falso. Si se da valor al parámetro opcional start, la comprobación empieza en esa posición. Si se da valor al parámetro opcional end, la comprobación finaliza en esa posición.

2.7. startswith (prefix[, start[, end]]) : devuelve verdadero si la cadena comienza por prefix, en caso contrario, devuelve falso. Si se proporciona el parámetro opcional start, se comprueba la cadena que empieza en esa posición. Si se proporciona el parámetro opcional end, se comprueba la cadena hasta esa posición.

Usando métodos con Regexp (4)

3. Ahora, para terminar, vamos a implementar dos métodos más o menos complejos para hacer dos tareas: encontrar y enlistar objetos (o findall) y substracción de objetos (o sub) dentro de una cadena, esto es:

3.1. re.findall(r'\b[p|P][a-z]*', Cadena01)

3.2. re.sub(r'(\b[a-z]+) \1', r'\1', Cadena01)

¿Qué nos arroja como resultado? Veamos:

Usando métodos con Regexp (5)

re.findall(r'\b[p|P][a-z]*', Cadena01)

Usando métodos con Regexp (6)

re.sub(r'(\b[a-z]+) \1', r'\1', Cadena01)

Usando métodos con Regexp (7)