lunedì 21 marzo 2016

Progetto Java: Ricercare Informazione in Grafi Sintattici

Commenti in merito al  progetti in java: Ricerca Informazione in Grafi Sintattici consegnati al prof Zanzotto.

Parte I - Uno stile migliore

1
2
3
if (labels.get(i).equals("nsubj")) {
  return new Word("nsubj", words.get(i));
}

In questo piccolo "pezzo" di codice possiamo apportare due importanti modifiche:

  1. Attuare un pattern di programmazione difensiva (Vedi http://lmpcoding.blogspot.it/2016/03/progetto-in-java-cruciverba-i-buoni.html)
  2. Utilizzare le costanti al posto delle stringhe
Il primo pensiero che deve venire in mente al momento della scrittura di un blocco "if-else" è 
Devo evitare assolutamente le condizioni che possono portare ad un errore
Nel nostro caso è importante proteggersi dall'istruzione
labels.get(i).equals("nsubj")
che può ritornare un valore pari a null: a runtime può succedere che il valore ritornato dalla get sia pari a null generando l'istruzione
null.equals("nsubj")
sollevando una NullPointerException.
Nei casi in cui bisogna effettuare dei controlli di uguaglianza su stringhe è sufficiente, per evitare situazioni di errore, eseguire il seguente "trick"

1
2
3
if ("nsubj".equals(labels.get(i))) {
  return new Word("nsubj", words.get(i));
}

In questo modo l'istruzione che verrà eseguita, nel caso in cui "labels.get(i)" sia pari a null, sarà
"nsubj".equals(null)
che restituirà semplicemente false.

Il secondo punto, per quanto banale possa essere, è importante al fine di diminuire la dipendenza tra le classi e gli oggetti che utilizziamo (in questo caso le stringhe).
Supponiamo che la stringa "nsubj"  cambi e si trasformi in "nsubje". A causa di questa modifica dobbiamo cambiare tutte le istruzioni in cui utilizziamo "a forza bruta" tale stringa.

Aggiungendo una costante alla classe, invece, sarà necessario modificare all'occorrenza solamente il valore di tale costante. Il codice precedente sarà quindi:

public class NodeFactory {

private static final String N_SUBJ_COSTANT = "nsubj";
...

if (N_SUBJ_COSTANT.equals(labels.get(i))) {
  return new Word(N_SUBJ_COSTANT words.get(i));
}


Parte II - Un unico metodo per domarli tutti

In ottica del principio "meno scrivo, meno devo modificare in futuro",  analizziamo i seguenti metodi:

public List<String> returnLabels(String sentence, LexicalizedParser lp) {

List<CoreLabel> rawWords = Sentence.toCoreLabelList(sentence);
Tree parse = lp.apply(rawWords);

 TokenizerFactory<CoreLabel> tokenizerFactory = PTBTokenizer.factory(new CoreLabelTokenFactory(), "");
 Tokenizer<CoreLabel> tok = tokenizerFactory.getTokenizer(new StringReader(sentence));
 List<CoreLabel> rawWords2 = tok.tokenize();
 parse = lp.apply(rawWords2);

 // PennTreebankLanguagePack for English
 TreebankLanguagePack tlp = lp.treebankLanguagePack();
 GrammaticalStructureFactory gsf = tlp.grammaticalStructureFactory();
 GrammaticalStructure gs = gsf.newGrammaticalStructure(parse);
 List<TypedDependency> tdl = gs.typedDependenciesCCprocessed();

 String lista = null;
 List<String> listaGrammaticale = new ArrayList<String>();
 for (int i = 0; i < tdl.size(); i++) {
  lista = tdl.get(i).toString();
  listaGrammaticale.add(lista);
 }
 List<String> listOfWords = new ArrayList<String>();
 for (int j = 0; j < listaGrammaticale.size(); j++) {

  if (listaGrammaticale.get(j).startsWith("nmod") || listaGrammaticale.get(j).startsWith("nsubj")
    || listaGrammaticale.get(j).startsWith("dobj")) {
   String partOfTheSentence = listaGrammaticale.get(j);
   if (listaGrammaticale.get(j).startsWith("nmod")) {
    String[] parts = partOfTheSentence.split("[:]");
    listOfWords.add(parts[0]);
   } else {
    String[] part = partOfTheSentence.split("[(]");
    listOfWords.add(part[0]);
   }
  }
 }
 return listOfWords;
}
..
public List<String> returnWords(String sentence, LexicalizedParser lp) {

List<CoreLabel> rawWords = Sentence.toCoreLabelList(sentence);
Tree parse = lp.apply(rawWords);

 TokenizerFactory<CoreLabel> tokenizerFactory = PTBTokenizer.factory(new CoreLabelTokenFactory(), "");
 Tokenizer<CoreLabel> tok = tokenizerFactory.getTokenizer(new StringReader(sentence));
 List<CoreLabel> rawWords2 = tok.tokenize();
 parse = lp.apply(rawWords2);

 // PennTreebankLanguagePack for English
 TreebankLanguagePack tlp = lp.treebankLanguagePack();
 GrammaticalStructureFactory gsf = tlp.grammaticalStructureFactory();
 GrammaticalStructure gs = gsf.newGrammaticalStructure(parse);
 List<TypedDependency> tdl = gs.typedDependenciesCCprocessed();

 String lista = null;
 List<String> listaGrammaticale = new ArrayList<String>();
 for (int i = 0; i < tdl.size(); i++) {
  lista = tdl.get(i).toString();
  listaGrammaticale.add(lista);
 }
 List<String> listOfWords = new ArrayList<String>();
 for (int j = 0; j < listaGrammaticale.size(); j++) {

  if (listaGrammaticale.get(j).startsWith("nmod") || listaGrammaticale.get(j).startsWith("nsubj")
    || listaGrammaticale.get(j).startsWith("dobj")) {
   String partOfTheSentence = listaGrammaticale.get(j);
   if (listaGrammaticale.get(j).startsWith("nmod")) {
    String[] parts = partOfTheSentence.split("[:]");
    listOfWords.add(parts[1]);
   } else {
    String[] part = partOfTheSentence.split("[(]");
    listOfWords.add(part[1]);
   }
  }
 }
 return listOfWords;
}

Guardandoli da vicino si nota che sono praticamente uguali a meno delle due line di codice:
listOfWords.add(parts[0]);listOfWords.add(parts[1]);
Questo implica che, guardando al futuro, se dovesse cambiare la logica dell'inizializzazione delle strutture sintattiche siamo costretti a modificarle in entrambi i metodi. Per risolvere questo problema possiamo creare un metodo di supporto che si occupa di inizializzare la lista grammaticale

public List<String> inizializeTreeDependencyList(String sentence, LexicalizedParser lp) {
List<CoreLabel> rawWords = Sentence.toCoreLabelList(sentence); Tree parse = lp.apply(rawWords); TokenizerFactory<CoreLabel> tokenizerFactory = PTBTokenizer.factory(new CoreLabelTokenFactory(), ""); Tokenizer<CoreLabel> tok = tokenizerFactory.getTokenizer(new StringReader(sentence)); List<CoreLabel> rawWords2 = tok.tokenize(); parse = lp.apply(rawWords2); // PennTreebankLanguagePack for English TreebankLanguagePack tlp = lp.treebankLanguagePack(); GrammaticalStructureFactory gsf = tlp.grammaticalStructureFactory(); GrammaticalStructure gs = gsf.newGrammaticalStructure(parse); List<TypedDependency> tdl = gs.typedDependenciesCCprocessed(); String lista = null; List<String> listaGrammaticale = new ArrayList<String>(); for (int i = 0; i < tdl.size(); i++) { lista = tdl.get(i).toString(); listaGrammaticale.add(lista); } return listaGrammaticale; }

I metodi
returnWords(...) e returnLabels(...)
verranno trasformati in

public List<String> returnLabels(String sentence, LexicalizedParser lp) {

List<String> listaGrammaticale = inizializeTreeDependencyList(sentence,lp);

List<String> listOfWords = new ArrayList<String>();
for (int j = 0; j < listaGrammaticale.size(); j++) {

 if (listaGrammaticale.get(j).startsWith("nmod") || listaGrammaticale.get(j).startsWith("nsubj")
   || listaGrammaticale.get(j).startsWith("dobj")) {
  String partOfTheSentence = listaGrammaticale.get(j);
  if (listaGrammaticale.get(j).startsWith("nmod")) {
   String[] parts = partOfTheSentence.split("[:]");
   listOfWords.add(parts[0]);
  } else {
   String[] part = partOfTheSentence.split("[(]");
   listOfWords.add(part[0]);
  }
 }
}
return listOfWords;
}
..
public List<String> returnWords(String sentence, LexicalizedParser lp) {

List<String> listaGrammaticale = inizializeTreeDependencyList(sentence,lp);
List<String> listOfWords = new ArrayList<String>();
for (int j = 0; j < listaGrammaticale.size(); j++) {

 if (listaGrammaticale.get(j).startsWith("nmod") || listaGrammaticale.get(j).startsWith("nsubj")
   || listaGrammaticale.get(j).startsWith("dobj")) {
  String partOfTheSentence = listaGrammaticale.get(j);
  if (listaGrammaticale.get(j).startsWith("nmod")) {
   String[] parts = partOfTheSentence.split("[:]");
   listOfWords.add(parts[1]);
  } else {
   String[] part = partOfTheSentence.split("[(]");
   listOfWords.add(part[1]);
  }
 }
}
return listOfWords;
}



Parte III - Modularizzare

I metodi 
returnWords(...) e returnLabels(...)
descritti precedentemente sono stati presi dalla classe NodeFactory. Analizzando la classe Relation facente parte dello stesso progetto  notiamo che il metodo

public void recognizeVerb(String sentence, LexicalizedParser lp) {
List<CoreLabel> rawWords = Sentence.toCoreLabelList(sentence);
Tree parse = lp.apply(rawWords);

TokenizerFactory<CoreLabel> tokenizerFactory = PTBTokenizer.factory(new CoreLabelTokenFactory(), "");
Tokenizer<CoreLabel> tok = tokenizerFactory.getTokenizer(new StringReader(sentence));
List<CoreLabel> rawWords2 = tok.tokenize();
parse = lp.apply(rawWords2);

// PennTreebankLanguagePack for English
TreebankLanguagePack tlp = lp.treebankLanguagePack();
GrammaticalStructureFactory gsf = tlp.grammaticalStructureFactory();
GrammaticalStructure gs = gsf.newGrammaticalStructure(parse);
List<TypedDependency> tdl = gs.typedDependenciesCCprocessed();

String lista = null;
List<String> listaGrammaticale = new ArrayList<String>();
for (int i = 0; i < tdl.size(); i++) {
 lista = tdl.get(i).toString();
 listaGrammaticale.add(lista);
}

for (int j = 0; j < listaGrammaticale.size(); j++) {
 if (listaGrammaticale.get(j).startsWith("root")) {
  String partOfTheSentence = listaGrammaticale.get(j);
  System.out.println(partOfTheSentence);
  String[] parts = partOfTheSentence.split("[(]");
  setLabel(parts[0]);
  setName(parts[1]);

 }
}

}

presenta le stesse line di codice utilizzate nella classe NodeFactory. Trovarci in questa situazione
non significa solamente trovarsi in presenza di codice duplicato all'interno di una classe ma anche tra classi diverse ,"sporcando" ancora di più il nostre codice. In caso di cambiamenti siamo costretti a cercare all'interno del progetto tutte le classi che utilizzano quella determinata sequenza di istruzioni e modificarle: operazione soggetta ad errori e sinonimo di una "cattiva" progettazione delle classi.

Per ovviare al problema è possibile creare un classe a parte per la gestione dell'interfacciamento con le classi del parser di Stanford. In questa classe posizioneremo tutti quei metodi di utilità per il parser.

public class StanfordParserUtils {
 
 

 public StanfordParserUtils() {
  ...
 }


 public List<String> inizializeTreeDependencyList(
            String sentence, LexicalizedParser lp) {

  List<CoreLabel> rawWords = Sentence.toCoreLabelList(sentence);
  Tree parse = lp.apply(rawWords);

  TokenizerFactory<CoreLabel> tokenizerFactory = PTBTokenizer.factory(new CoreLabelTokenFactory(), "");
  Tokenizer<CoreLabel> tok = tokenizerFactory.getTokenizer(new StringReader(sentence));
  List<CoreLabel> rawWords2 = tok.tokenize();
  parse = lp.apply(rawWords2);

  // PennTreebankLanguagePack for English
  TreebankLanguagePack tlp = lp.treebankLanguagePack();
  GrammaticalStructureFactory gsf = tlp.grammaticalStructureFactory();
  GrammaticalStructure gs = gsf.newGrammaticalStructure(parse);
  List<TypedDependency> tdl = gs.typedDependenciesCCprocessed();

  String lista = null;
  List<String> listaGrammaticale = new ArrayList<String>();
  for (int i = 0; i < tdl.size(); i++) {
   lista = tdl.get(i).toString();
   listaGrammaticale.add(lista);
  }

  return listaGrammaticale;
 }

}

Cosi facendo, ogni qual volta dobbiamo eseguire un parser di un frase attraverso le strutture del Parser di Stanford, basterà chiamare il metodo
inizializeTreeDependencyList(...)

all'interno della classe StanfordParserUtils().


Parte IV - Attenti al "Cast"

Analizziamo il seguente codice:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public List<Variable> relationMatch (Relation a){
List<Variable> ls = new Vector<>();
if ( this.label == a.label){
 if (a.in.getClass().equals(Variable.class)){
  if(a.out.getClass().equals(Variable.class)){
   ls.add(new Variable( ((Variable)a.in).name , new Word(((Word)this.in).surface)));
   ls.add(new Variable( ((Variable)a.out).name , new Word(((Word)this.out).surface)));
   return ls;
  }
  else if (((Word)a.out).surface == ((Word)this.out).surface){
   ls.add(new Variable( ((Variable)a.in).name , new Word(((Word)this.in).surface)));
   return ls;
  }
 }
 else if (((Word)a.in).surface == ((Word)this.in).surface) {
  if(a.out.getClass().equals(Variable.class)){
   ls.add(new Variable( ((Variable)a.out).name , new Word(((Word)this.out).surface)));
   return ls;
  }
 }
}
return null;
}

Alla riga 6,7,11,17 ci sono una serie di cast che devono essere controllati prima di essere eseguiti. Potrebbe succedere che le variabili che si stanno castando non sono le variabili oggetto del cast, generando una situazione di errore. In questi casi è necessario utilizzare, prima del cast, l'operatore instanceof 



...
if(a instanceof Variable && in instanceof Word){
 ls.add(new Variable( ((Variable)a.in).name , new Word(((Word)this.in).surface)));
...
}
...

Nessun commento:

Posta un commento