Python para PNL: Primeros pasos con la biblioteca StanfordCoreNLP

Este es el noveno artículo de mi serie de artículos sobre Python para PNL. En el artículo anterior, vimos cómo se puede usar la biblioteca de patrones de Python para realizar una va...

Este es el noveno artículo de mi serie de artículos sobre Python para PNL. En el Artículo anterior, vimos cómo la biblioteca Pattern de Python se puede usar para realizar una variedad de tareas de PNL que van desde la tokenización al etiquetado de POS y clasificación de texto al análisis de sentimientos. Antes de eso, exploramos la biblioteca TextBlob para realizar tareas similares de procesamiento de lenguaje natural.

En este artículo, exploraremos la biblioteca StanfordCoreNLP, que es otra biblioteca extremadamente útil para el procesamiento del lenguaje natural. Veremos diferentes características de StanfordCoreNLP con la ayuda de ejemplos. Entonces, antes de perder más tiempo, comencemos.

Configuración del entorno

El proceso de instalación de StanfordCoreNLP no es tan sencillo como el de otras bibliotecas de Python. De hecho, StanfordCoreNLP es una biblioteca que en realidad está escrita en Java. Por lo tanto, asegúrese de tener Java instalado en su sistema. Puedes descargar la última versión de Java libremente.

Una vez que haya instalado Java, debe descargar los archivos JAR para las bibliotecas de StanfordCoreNLP. El archivo JAR contiene modelos que se utilizan para realizar diferentes tareas de NLP. Para descargar los archivos JAR de los modelos en inglés, descargue y descomprima la carpeta ubicada en el sitio web oficial StanfordCoreNLP.

Lo siguiente que debe hacer es ejecutar el servidor que atenderá las solicitudes enviadas por el contenedor de Python a la biblioteca StanfordCoreNLP. Navegue hasta la ruta donde descomprimió la carpeta de archivos JAR. Navegue dentro de la carpeta y ejecute el siguiente comando en el símbolo del sistema:

1
$ java -mx6g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -timeout 10000

El comando anterior inicia el servidor StanfordCoreNLP. El parámetro -mx6g especifica que la memoria utilizada por el servidor no debe exceder los 6 gigabytes. Es importante mencionar que debe ejecutar un sistema de 64 bits para tener un montón de 6 GB. Si está ejecutando un sistema de 32 bits, es posible que deba reducir el tamaño de la memoria dedicada al servidor.

Una vez que ejecute el comando anterior, debería ver el siguiente resultado:

1
2
3
4
5
6
7
8
9
[main] INFO CoreNLP - --- StanfordCoreNLPServer#main() called ---
[main] INFO CoreNLP - setting default constituency parser
[main] INFO CoreNLP - warning: cannot find edu/stanford/nlp/models/srparser/englishSR.ser.gz
[main] INFO CoreNLP - using: edu/stanford/nlp/models/lexparser/englishPCFG.ser.gz instead
[main] INFO CoreNLP - to use shift reduce parser download English models jar from:
[main] INFO CoreNLP - http://stanfordnlp.github.io/CoreNLP/download.html
[main] INFO CoreNLP -     Threads: 8
[main] INFO CoreNLP - Starting server...
[main] INFO CoreNLP - StanfordCoreNLPServer listening at /0:0:0:0:0:0:0:0:9000

El servidor se está ejecutando en el puerto 9000.

Ahora, el paso final es instalar el envoltorio de Python para la biblioteca StanfordCoreNLP. El contenedor que usaremos es pycorenlp. El siguiente script descarga la biblioteca contenedora:

1
$ pip install pycorenlp

Ahora estamos listos para conectarnos al servidor StanfordCoreNLP y realizar las tareas de PNL deseadas.

Para conectarnos al servidor, tenemos que pasar la dirección del servidor StanfordCoreNLP que inicializamos anteriormente a la clase StanfordCoreNLP del módulo pycorenlp. El objeto devuelto se puede usar para realizar tareas de NLP. Mira el siguiente guión:

1
2
3
from pycorenlp import StanfordCoreNLP

nlp_wrapper = StanfordCoreNLP('http://localhost:9000')

Realización de tareas de PNL

En esta sección, exploraremos brevemente el uso de la biblioteca StanfordCoreNLP para realizar tareas comunes de PNL.

Lematización, etiquetado de POS y reconocimiento de entidad nombrada

La lematización, el etiquetado de partes del discurso y el reconocimiento de entidades nombradas son las tareas más básicas de NLP. La biblioteca StanfordCoreNLP admite la funcionalidad de canalización que se puede utilizar para realizar estas tareas de forma estructurada.

En el siguiente script, crearemos un anotador que primero divide un documento en oraciones y luego divide las oraciones en palabras o tokens. Luego, las palabras se anotan con las etiquetas de reconocimiento de entidad nombrada y POS.

1
2
3
4
5
6
7
doc = "Ronaldo has moved from Real Madrid to Juventus. While messi still plays for Barcelona"
annot_doc = nlp_wrapper.annotate(doc,
    properties={
        'annotators': 'ner, pos',
        'outputFormat': 'json',
        'timeout': 1000,
    })

En el guión de arriba tenemos un documento con dos oraciones. Usamos el método anotar del objeto contenedor StanfordCoreNLP que inicializamos anteriormente. El método toma tres parámetros. El parámetro annotator toma el tipo de anotación que queremos realizar en el texto. Pasamos 'ner, pos' como valor para el parámetro annotator que especifica que queremos anotar nuestro documento para etiquetas POS y entidades con nombre.

La variable outputFormat define el formato en el que desea el texto anotado. Los valores posibles son json para objetos JSON, xml para formato XML, text para texto sin formato y serialize para datos serializados.

El parámetro final es el tiempo de espera en milisegundos que define el tiempo que el contenedor debe esperar la respuesta del servidor antes de que se agote el tiempo de espera.

En la salida, debería ver un objeto JSON de la siguiente manera:

{'sentences': [{'index': 0,
   'entitymentions': [{'docTokenBegin': 0,
     'docTokenEnd': 1,
     'tokenBegin': 0,
     'tokenEnd': 1,
     'text': 'Ronaldo',
     'characterOffsetBegin': 0,
     'characterOffsetEnd': 7,
     'ner': 'PERSON'},
    {'docTokenBegin': 4,
     'docTokenEnd': 6,
     'tokenBegin': 4,
     'tokenEnd': 6,
     'text': 'Real Madrid',
     'characterOffsetBegin': 23,
     'characterOffsetEnd': 34,
     'ner': 'ORGANIZATION'},
    {'docTokenBegin': 7,
     'docTokenEnd': 8,
     'tokenBegin': 7,
     'tokenEnd': 8,
     'text': 'Juventus',
     'characterOffsetBegin': 38,
     'characterOffsetEnd': 46,
     'ner': 'ORGANIZATION'}],
   'tokens': [{'index': 1,
     'word': 'Ronaldo',
     'originalText': 'Ronaldo',
     'lemma': 'Ronaldo',
     'characterOffsetBegin': 0,
     'characterOffsetEnd': 7,
     'pos': 'NNP',
     'ner': 'PERSON',
     'before': '',
     'after': ' '},
    {'index': 2,
     'word': 'has',
     'originalText': 'has',
     'lemma': 'have',
     'characterOffsetBegin': 8,
     'characterOffsetEnd': 11,
     'pos': 'VBZ',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 3,
     'word': 'moved',
     'originalText': 'moved',
     'lemma': 'move',
     'characterOffsetBegin': 12,
     'characterOffsetEnd': 17,
     'pos': 'VBN',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 4,
     'word': 'from',
     'originalText': 'from',
     'lemma': 'from',
     'characterOffsetBegin': 18,
     'characterOffsetEnd': 22,
     'pos': 'IN',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 5,
     'word': 'Real',
     'originalText': 'Real',
     'lemma': 'real',
     'characterOffsetBegin': 23,
     'characterOffsetEnd': 27,
     'pos': 'JJ',
     'ner': 'ORGANIZATION',
     'before': ' ',
     'after': ' '},
    {'index': 6,
     'word': 'Madrid',
     'originalText': 'Madrid',
     'lemma': 'Madrid',
     'characterOffsetBegin': 28,
     'characterOffsetEnd': 34,
     'pos': 'NNP',
     'ner': 'ORGANIZATION',
     'before': ' ',
     'after': ' '},
    {'index': 7,
     'word': 'to',
     'originalText': 'to',
     'lemma': 'to',
     'characterOffsetBegin': 35,
     'characterOffsetEnd': 37,
     'pos': 'TO',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 8,
     'word': 'Juventus',
     'originalText': 'Juventus',
     'lemma': 'Juventus',
     'characterOffsetBegin': 38,
     'characterOffsetEnd': 46,
     'pos': 'NNP',
     'ner': 'ORGANIZATION',
     'before': ' ',
     'after': ''},
    {'index': 9,
     'word': '.',
     'originalText': '.',
     'lemma': '.',
     'characterOffsetBegin': 46,
     'characterOffsetEnd': 47,
     'pos': '.',
     'ner': 'O',
     'before': '',
     'after': ' '}]},
  {'index': 1,
   'entitymentions': [{'docTokenBegin': 14,
     'docTokenEnd': 15,
     'tokenBegin': 5,
     'tokenEnd': 6,
     'text': 'Barcelona',
     'characterOffsetBegin': 76,
     'characterOffsetEnd': 85,
     'ner': 'ORGANIZATION'}],
   'tokens': [{'index': 1,
     'word': 'While',
     'originalText': 'While',
     'lemma': 'while',
     'characterOffsetBegin': 48,
     'characterOffsetEnd': 53,
     'pos': 'IN',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 2,
     'word': 'messi',
     'originalText': 'messi',
     'lemma': 'messus',
     'characterOffsetBegin': 54,
     'characterOffsetEnd': 59,
     'pos': 'NNS',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 3,
     'word': 'still',
     'originalText': 'still',
     'lemma': 'still',
     'characterOffsetBegin': 60,
     'characterOffsetEnd': 65,
     'pos': 'RB',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 4,
     'word': 'plays',
     'originalText': 'plays',
     'lemma': 'play',
     'characterOffsetBegin': 66,
     'characterOffsetEnd': 71,
     'pos': 'VBZ',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 5,
     'word': 'for',
     'originalText': 'for',
     'lemma': 'for',
     'characterOffsetBegin': 72,
     'characterOffsetEnd': 75,
     'pos': 'IN',
     'ner': 'O',
     'before': ' ',
     'after': ' '},
    {'index': 6,
     'word': 'Barcelona',
     'originalText': 'Barcelona',
     'lemma': 'Barcelona',
     'characterOffsetBegin': 76,
     'characterOffsetEnd': 85,
     'pos': 'NNP',
     'ner': 'ORGANIZATION',
     'before': ' ',
     'after': ''}]}]}

Si observa detenidamente el script anterior, puede encontrar las etiquetas POS, las entidades nombradas y la versión lematizada de cada palabra.

Lematización

Exploremos ahora los resultados anotados. Primero imprimiremos las lematizaciones de las palabras en las dos oraciones en nuestro conjunto de datos:

1
2
3
for sentence in annot_doc["sentences"]:
    for word in sentence["tokens"]:
        print(word["word"] + " => " + word["lemma"])

En el script anterior, el ciclo externo itera a través de cada oración en el documento y el ciclo interno itera a través de cada palabra en la oración. Dentro del bucle interno, la palabra y su forma lematizada correspondiente se imprimen en la consola. La salida se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Ronaldo=>Ronaldo
has=>have
moved=>move
from=>from
Real=>real
Madrid=>Madrid
to=>to
Juventus=>Juventus
.=>.
While=>while
messi=>messus
still=>still
plays=>play
for=>for
Barcelona=>Barcelona

Por ejemplo, puede ver que la palabra “movido” se lematizó como “mover”, de manera similar, la palabra “juega” se lematizó como “jugar”.

Etiquetado de POS

De la misma manera, podemos encontrar las etiquetas POS para cada palabra. Mira el siguiente guión:

1
2
3
for sentence in annot_doc["sentences"]:
    for word in sentence["tokens"]:
        print (word["word"] + "=>" + word["pos"])

En la salida, debería ver los siguientes resultados:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Ronaldo=>NNP
has=>VBZ
moved=>VBN
from=>IN
Real=>JJ
Madrid=>NNP
to=>TO
Juventus=>NNP
.=>.
While=>IN
messi=>NNS
still=>RB
plays=>VBZ
for=>IN
Barcelona=>NNP

El conjunto de etiquetas utilizado para las etiquetas POS es el conjunto de etiquetas Penn Treebank y se puede encontrar aquí.

Reconocimiento de entidad nombrada

Para encontrar entidades con nombre en nuestro documento, podemos usar el siguiente script:

1
2
3
for sentence in annot_doc["sentences"]:
    for word in sentence["tokens"]:
        print (word["word"] + "=>" + word["ner"])

La salida se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Ronaldo=>PERSON
has=>O
moved=>O
from=>O
Real=>ORGANIZATION
Madrid=>ORGANIZATION
to=>O
Juventus=>ORGANIZATION
.=>O
While=>O
messi=>O
still=>O
plays=>O
for=>O
Barcelona=>ORGANIZATION

Podemos ver que Ronaldo se ha identificado como PERSONA mientras Barcelona se ha identificado como Organización, lo que en este caso es correcto.

Análisis de sentimiento

Para encontrar el sentimiento de una oración, todo lo que tiene que hacer es pasar sentimiento como el valor de la propiedad anotadores. Mira el siguiente guión:

1
2
3
4
5
6
7
doc = "I like this chocolate. This chocolate is not good. The chocolate is delicious. Its a very tasty chocolate. This is so bad"
annot_doc = nlp_wrapper.annotate(doc,
    properties={
       'annotators': 'sentiment',
       'outputFormat': 'json',
       'timeout': 1000,
    })

Para encontrar el sentimiento, podemos iterar sobre cada oración y luego usar la propiedad sentimentValue para encontrar el sentimiento. sentimentValue devuelve un valor entre 1 y 4, donde 1 corresponde a una opinión muy negativa, mientras que 4 corresponde a una opinión muy positiva. La propiedad ‘sentimiento’ se puede utilizar para obtener un sentimiento en forma verbal, es decir, ‘positivo’, ’negativo’ o ’neutral’.

El siguiente script encuentra el sentimiento de cada oración en el documento que definimos anteriormente.

1
2
3
for sentence in annot_doc["sentences"]:
    print ( " ".join([word["word"] for word in sentence["tokens"]]) + " => " \
        + str(sentence["sentimentValue"]) + " = "+ sentence["sentiment"])

Producción:

1
2
3
4
5
I like this chocolate . => 2 = Neutral
This chocolate is not good . => 1 = Negative
The chocolate is delicious . => 3 = Positive
Its a very tasty chocolate . => 3 = Positive
This is so bad => 1 = Negative

Conclusión

StanfordCoreNLP es otra biblioteca extremadamente útil para el procesamiento del lenguaje natural. En este artículo, estudiamos cómo configurar el entorno para ejecutar StanfordCoreNLP. Luego exploramos el uso de la biblioteca StanfordCoreNLP para tareas comunes de NLP, como la lematización, el etiquetado de POS y el reconocimiento de entidades nombradas y, finalmente, completamos el artículo con un análisis sentimental utilizando StanfordCoreNLP.