WMR 12 - Lab II (Standford coreNLP + Spacy)


Lecture Info


In questo laboratorio abbiamo visto come utilizzare le informazioni ottenute dal processo di parsing morfo-sintattico per risolvere un particolare task tramite l'utilizzo di due sistemi: Spacy e Standford CoreNLP.

1 Standford CoreNLP

Sistema open-source scritto in Java, è il risultato del migliore gruppo gruppo che si occupa di analisi morfo-sintattica. Vince quasi sistematicamente le competizioni a livello mondiale.

Alcuni link utili:

Questo sistema definisce una pipeline di processi, rappresentata dal seguente diagramma

Osserviamo come tale sistema non permette solamente di estrarre dal testo delle informazioni morfo-sintattiche, ma permette anche di inferire informazioni sul sentiment, sul gender, e su altre tipologie di informazioni che hanno a che fare più sulla semantica che sulla sintassi.

Consideriamo il seguente esempio di utilizzo, in cui vengono mostrato il PoS tagging e i lemmi.


1.1 How to Use it

Questo sistema può essere integrato in un qualsiasi progetto Java gestito con maven andando ad aggiungere la seguente dipendenza al file pom.xml .

<dependencies>
  <dependency>
    <groupId>edu.stanford.nlp</groupId>
    <artifactId>stanford-corenlp</artifactId>
    <version>3.6.0</version>
  </dependency>

  <dependency>
    <groupId>edu.stanford.nlp</groupId>
    <artifactId>stanford-corenlp</artifactId>
    <version>3.6.0</version>
    <classifier>models</classifier>
  </dependency>
</dependencies>


1.2 The CONNL Tabular Format

Una volta importato possiamo utilizzarlo come segue

Properties props = new Properties();
props.put("annotators", "tokenize, spplit, pos, lemma, ner, parse");

StanfordCoreNLP pipeline = new StanfordCoreNLP(props);
String str = "In 1982, Mark drove his car from Los Angeles to Las Vegas";

Annotation document = new Annotation(str);

pipeline.annotate(document);
List<CoreMap> sentences = document.get(SentencesAnnotation.class);
// for each sentence
for (CoreMap sentence : sentences) {
    // dependency graph
    SemanticGraph dependencies = sentence.get(BasicDependenciesAnnotation.class);
    // For each token
    for (CoreLabel token : sentence.get(TokensAnnotation.class)) {
        IndexedWord sourceNode = dependencies.getNodeByIndex(token.index());
        IndexedWord father = dependencies.getParent(sourceNode);

        // If no father, then we have root
        int fatherId = 0;
        String relName = "root";
        if (father != null) {
            fatherId = father.index();
            SemanticGraphEdge edge = dependencies.getEdge(father, sourceNode);
            relName = edge.getRelation().getShortName();
        }

        System.out.println(token.index() + "\t" + token.originalText() +
                           "\t" + token.lemma() + "\t" + token.tag() + "\t" +
                           token.ner() + "\t" + relName + "\t" + fatherId);
    }
    System.out.println();
}

La forma tabellare per rappresentare il grafo alle dipendenze di una frase è detto CONLL tabular format in quanto è il formato utilizzato per rilasciare i benchmark organizzati dalla conferenza CONLL.

Per ogni riga della tabella ho le seguenti informazioni

  • tokenID

  • surface

  • lemma

  • pos

  • named entity

  • relation name

  • dependant

2 Spacy

Un'altra libreria per fare NLP è spacy, che è implementato in python.


2.1 Anaconda

Python ha una gestione delle librerie abbastanza peculiare, in quanto installa all'interno di una cartella tutte le librerie di cui ha bisogno. Il problema di questo approccio è che non si interessa della retro-compatibilità, e quando vengono modificate le librerie non si ha la sicurezza di essere retro-compatibili.

Per risolvere questi problemi e avere la sicurezza di installare una particolare versione di una libreria, si utilizzano i gestori dei pacchetti. In particolare si definisco degli ambienti di lavoro, detti anche environments, in modo tale che si possono utilizzare diverse versioni della stessa libreria su diversi environment.

Un esempio di gestore dei pacchetti è anaconda, che permette la gestione di ambienti di sviluppo python, ciascuno con le proprie dipendenze. Per creare un nuovo ambiente di sviluppo con anaconda possiamo utilizzare i seguenti comandi

conda create -n spacyenv
conda activate spacyenv
conda install spacy
python -m spacy download en


2.2 Basic Example

Andiamo a stampare le informazioni che abbiamo visto prima utilizzando la libreria StanfordNLP in Java.

import spacy
nlp = spacy.load('en')
doc = nlp('In 1982, Mark drove his car from Los Angeles to Las Vegas')

for sent in doc.sents:
    for i, word in enumerate(sent):
        if word.head is word:
            head_idx = 0
        else:
            head_idx = doc[i].head.i + 1
            if head_idx == i + 1:
                head_idx = 0

        entity_tag = word.ent_type_
        if len(entity_tag) == 0:
            entity_tag = "O"

        print("%d\t%s\t%s\t%s\t%s\t%s\t%d" %
              (i + 1, word, word.lemma_, word.tag_, entity_tag,
               word.dep_, head_idx))

A questo punto possiamo utilizzare le informazioni morfo-sintattiche ottenute dal parser Spacy per andare ad effettuare vari tasks. Tra questi task possiamo considerare i seguenti:

  • Derivare features per arricchire la rappresentazione per un classificatore di tipo Naive Bayes.

  • Estrarre tutte le triple della forma (soggetto, verbo, oggetto diretto) da una collezione documentale (es: parole di Wikipedia).

3 Example (Wikipedia words)

Supponiamo di avere una serie di frasi riprese da wikipedia e parsate da coreNLP nel seguente modo

1       In      in      IN      O       case    2
2       1963    1963    CD      DATE    nmod    5
3       Joseph  Joseph  NNP     PERSON  compound        4
4       Greenberg       Greenberg       NNP     PERSON  nsubj   5
5       added   add     VBD     O       root    0
6       them    they    PRP     O       dobj    5
7       to      to      TO      O       case    9
8       the     the     DT      O       det     9
9       Niger   Niger   NNP     LOCATION        nmod    5
10      –       --      :       O       punct   9
11      Congo   Congo   NNP     LOCATION        compound        12
12      family  family  NN      O       dep     9
13      ,       ,       ,       O       punct   5
14      creating        create  VBG     O       advcl   5
15      his     he      PRP$    O       nmod:poss       16
16      Niger   Niger   NNP     LOCATION        dobj    14
17      –       --      :       O       punct   16
18      Kordofanian     kordofanian     JJ      MISC    amod    19
19      proposal        proposal        NN      O       dep     16
20      .       .       .       O       punct   5

dove come le varie colonne stanno per (in ordine)

index, surface, lemma, pos, named entity, relation name, dependant


3.1 Extracting Triples (Subject, Verb, Object)

Inizialmente posso quindi pensare di estrarre solamente le triple (soggetto, verbo, oggetto). Utilizzando lo script CoreNLP_subj-obj_pattern_extractor.py ottengo il seguente output.


3.2 Generalizing

Un primo approccio per generalizzare potrebbe essere quello di processare l'output dello script precedente con bash nel seguente modo

python CoreNLP_subj-obj_pattern_extractor.py ../wiki_en_20150114_parsed_first_1M.txt.gz | sort | uniq -c | sort -k 1

Volendo generalizzare meglio però possiamo utilizzare il named entity reconigized, e sostituire alle particolare istanze la loro named entity.

python CoreNLP_subj-obj_pattern_extractor_NE.py ../wiki_en_20150114_parsed_first_1M.txt.gz | sort | uniq -c | sort -k 1

4 Exercise: Q/A

Nel precedente laboratorio avevamo visto come in weka potevamo utilizzare la funzione String2WordVector per tokenizzare la frase ed assegnare dei pesi ad ogni token.

L'idea adesso è quella di riscrivere l'arff file andando a codificare ogni stringa come un vettore in cui le singole dimensioni sono le parole ottenute applicando il tokenizzatore di spacy.

Se al posto della parola metto il lemma, sto efettuando feature redaction.

Come posso utilizzare le informazioni sintattiche per aiutarmi nel processo di classificazione delle frasi? L'idea è quella di utilizare i bigrammi al posto degli unigrammi. In questo modo siamo in grado di iniettare piccole ma importanti informazioni sintattiche nel nostro modello.

Sfida: utilizzare spacy per costruire l'arf file utilizzando la rappresentazione vista a lezione, in cui dobbiamo:

  1. Capire il dizionario dei simboli leggendo tutto quanto il training set.

  2. Ad ogni indice della parola diamo un peso stando attenti al primo simbolo, che è legato alla classe. La prima pesatura può essere binaria e fatta sugli unigrammi, mentre la seconda pesatura può essere binaria e fatta sui bigrammi. Inoltre è anche possibile implementare una pesatura di tipo tf-idf.

NOTA: Nell'output prodotto da Weka una classe, la prima, non viene scritta in modo esplicito ma è intesa in modo implicito.

La Heap's law può essere utilizzata per stimare il size del dizionario rispetto al numero di token nella collezione.

Per cercare di gestire lo spazio di rappresentazione quando il dizionario è molto grande si utilizzano delle rappresentazioni implicite che permettono di gestire la dimensione dei dizionari. (kernel methods).

BERT non rappresenta le parole, ma dei pezzi di parole (tra parole e simboli), per cercare di migliorare le performance.

Q: Come aggiungiamo le informazioni sul POS tagging nella rappresentazioni delle domande?

R: L'idea è quella di sostiuire ogni parola con la struttura :, dove con intendiamo il lemma della parola, e con una o più lettere dalla classe POS ritornata dal nostro parser sintattico. Tramite benchmarks e testing si può capire qual'è la scelta di rappresentazione migliore.