Guia de aula laboratorial

O guia da 5º aula prática está disponível online. Os tópicos abordados incluem conceitos relacionados com a extracção de conhecimento através de técnicas de prospecção de texto.

O ficheiro extraccao-web.zip ( http://web.tagus.ist.utl.pt/~bruno.martins/materials/class-materials/labs-gti/extraccao-web/extraccao-web.zip ) inclui vários exemplos para os conceitos abordados nas aulas de laboratório referentes a extracção de informação.

Extracção de conhecimento

Uma componente importante de um processo de extracção de informação a partir de fontes não estruturadas relaciona-se com a análise computacional do texto. Entre as tarefas mais comuns encontram-se as seguintes:

  • Classificar passagens de texto de acordo com a língua ou com o tema (e.g. detectar notícias sobre desporto, economia ou política).
  • Encontrar menções a entidades no texto (e.g. nomes de pessoas, endereços de email, moradas) e relacionar estas entidades com entradas em bases de dados existentes.
Nesta aula de laboratório iremos estudar para a ferramenta LingPipe ( http://www.alias-i.com/lingpipe ), a qual se apresenta como um pacote Java para extracção de informação e prospecção de texto. Trata-se de uma ferramenta bastante rica, a qual apresenta alguma complexidade na sua utilização. Como tal, irão apenas ser abordados alguns exemplos de utilização simples. A quem tenha curiosidade, aconselha-se a consulta da documentação completa e tutoriais da ferramenta ( http://www.alias-i.com/lingpipe/demos/tutorial/read-me.html )

A ferramenta LingPipe não possui um utilitário de linha de comandos. A sua utilização faz-se sempre no contexto de uma aplicação Java. O exemplo que se segue mostra como o LingPipe pode ser utilizado para classificar uma dada String de acordo com a sua língua. Esta é uma tarefa essencial para muitas aplicações de processamento de texto, estando na base de operações de processamento mais complexas (e.g. permite por exemplo efectuar a selecção de um conjunto de regras de extracção de acordo com a língua do documento).

import com.aliasi.classify.*;

public class ExampleLangClassification {

public static String classify ( String text ) throws Exception {
// Treinar um classificador baseado em tri-gramas usando duas frases de texto
String[] categories = { "pt", "en" };
String[] exampleText = { "Frase em Português", "Sentence in English" };
DynamicLMClassifier classifier = DynamicLMClassifier.createNGramProcess(categories,3);
classifier.train(categories[0],exampleText[0].toCharArray(),0,exampleText[0].length());
classifier.train(categories[1],exampleText[1].toCharArray(),0,exampleText[1].length());
JointClassification jc = classifier.classifyJoint(text.toCharArray(),0,text.length());
return
jc.bestCategory();
}


public static void main(String[] args) throws Exception {
for (int i = 0; i < args.length; ++i) {
System.out.println("input=" + args[i]);
String bestCategory = classify(args[i]);
System.out.println("output=" + bestCategory);
}
}

}

O exemplo anterior faz uso de um método de aprendizagem automática (DynamicLMClassifier, acrónimo derivado de "classificador baseado num modelo de linguagem") para construir um modelo para a classificação de Strings a partir de duas Strings de exemplo. O classificador aprende um modelo para a distribuição de caracteres (na prática, conjuntos de um, dois ou três caracteres, i.e. tri-gramas) para as línguas Portuguesa e Inglesa, com base nas Strings de exemplo. A classificação baseia-se na ideia de que algumas sequências de caracteres (e.g. "ês") são mais frequentes numa língua do que na outra.

O algoritmo DynamicLMClassifier é na realidade uma generalização do popular algoritmo de classificação "Naive Bayes". Este último usa um processo estatístico com base no Teorema de Bayes, o qual diz que a probabilidade de um dado documento pertencer a uma classe, verificando-se a ocorrência de uma série de features (neste exemplo de classificação de acordo com a língua, trigramas de caracteres), é igual à probabilidade de encontrar aquela série de features num documento da mesma classe a multiplicar pela probabilidade de um dado documento ser da mesma classe e dividindo pela probabilidade de encontrar as mesmas feaures num documento de qualquer classe. No final do processo, escolhe-se a classe com maior probabilidade. O "naive" referece-se ao facto de ao calcular a probabilidade de ocorrência uma série de features, o algoritmo faz uma simplificação ao assumir a independência entre elas (i.e. o facto de uma palavra ocorrer no texto não depende em nada da ocorrência de outras palavras, o que na prática não se verifica).

P(classe|features) = ( P(features|classe) * P(classe) ) / P(features)

Mais informações sobre os algoritmos de classificação poderão ser obtidas na documentação da ferramenta LingPipe ou nos slides das aulas teóricas de GTI.

No que diz respeito à tarefa de encontrar menções a entidades em texto, a ferramenta LingPipe permite a utilização de métodos sofisticados baseados em aprendizagem automática, assim como métodos mais directos baseados em dicionários de entidades ou expressões regulares. O seguinte exemplo mostra como a ferramenta LingPipe pode ser utilizada para encontrar entidades (neste caso endereços de email) sobre um texto, com base na utilização de expressões regulares.


import com.aliasi.chunk.*;
import java.util.*;

public class ExampleFindEmails extends RegExChunker {

public ExampleFindEmails() {
super(EMAIL_REGEX,CHUNK_TYPE,CHUNK_SCORE);
}

private final static String EMAIL_REGEX = "[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})";
private final static String CHUNK_TYPE = "email";
private final static double CHUNK_SCORE = 1.0;

public static void main(String[] args) {
Chunker chunker = new ExampleFindEmails();
for (int i = 0; i < args.length; ++i) {
Chunking chunking = chunker.chunk(args[i]);
System.out.println("input=" + args[0]);
Set chunkSet = chunking.chunkSet();
Iterator it = chunkSet.iterator();
while (it.hasNext()) {
Chunk chunk = (Chunk) it.next();
int start = chunk.start();
int end = chunk.end();
String text = args[0].substring(start,end);
System.out.println(" extraccão=" + chunk + " valor=" + text);
}
}
}
}

Já o exemplo que se segue mostra como o LingPipe pode ser utilizado para encontrar entidades em texto com base num dicionário de palavras.

import com.aliasi.chunk.*;
import com.aliasi.dict.*;
import com.aliasi.tokenizer.*;
import java.util.*;

public class ExampleEntityRecognition {

static final double CHUNK_SCORE = 1.0;

public static void main(String[] args) {
// Define the dictionary
MapDictionary dictionary = new MapDictionary();
dictionary.addEntry(new DictionaryEntry("Bruno Martins","PERSON",CHUNK_SCORE));
dictionary.addEntry(new DictionaryEntry("Spatial Databases","DB_ID_1232",CHUNK_SCORE));
dictionary.addEntry(new DictionaryEntry("livro","PRODUCT",CHUNK_SCORE));
// Recognizer for dictionary entries (true,true=allmatches,case_sensitive)
ExactDictionaryChunker chunker = new ExactDictionaryChunker(dictionary,IndoEuropeanTokenizerFactory.FACTORY,true,true);
for (int i = 0; i < args.length; ++i) {
String text = args[i];
System.out.println("TEXT=" + text);
System.out.println("All matches=" + chunker.returnAllMatches() + " Case sensitive=" + chunker.caseSensitive());
Chunking chunking = chunker.chunk(text);
for (Chunk chunk : chunking.chunkSet()) {
int start = chunk.start();
int end = chunk.end();
String type = chunk.type();
double score = chunk.score();
String phrase = text.substring(start,end);
System.out.println("phrase=|" + phrase + "|" + " start=" + start + " end=" + end + " type=" + type + " score=" + score);
}
}
}

}

Exemplos mais detalhados da utilização da ferramenta LingPipe podem ser encontrados online no site da mesma ( http://www.alias-i.com/lingpipe/web/demos.html ).


Combinando as ferramentas Web-harvest e LingPipe

Uma forma muito simples de combinar a API da ferramenta LingPipe com processadores da ferramenta Web-harvest para extracção de informação (por exemplo para classificar o conteúdo textual dado como resultado de um outro processo de extracção) envolve a utilização do Web-harvest a partir de uma aplicação Java que passe um Objecto específico que faça chamadas a métodos na API do LingPipe como uma variável de contexto. O exemplo que se segue mostra como isso pode ser feito:


import org.webharvest.definition.ScraperConfiguration;
import org.webharvest.runtime.Scraper;

public class WebHarvestTest {

public static void main(String[] args) throws Exception {
ScraperConfiguration config = new ScraperConfiguration("webharvest-config.xml");
Scraper scraper = new Scraper(config, "./work-dir/");
scraper.addVariableToContext("langClassification", new ExampleLangClassification());
scraper.setDebug(true);
scraper.execute();
}

}


O objecto ExampleLangClassification passará assim a poder ser utilizado em qualquer expressão dada como parte de um processador no ficheiro de configuração do Web-harvest.

<var-def name="language">
<template>
${langClassification.getWrappedObject().
classify("texto de exemplo");}
</template>
</var-def>

Em alternativa, o Web-harvest também permite a chamada a métodos Java através de um processador que permite a utilização de linguagens de scripting. Mais informações poderão ser obtidas na documentação da ferramenta Web-harvest.

Links: