[Algoritmos] [PLN#5] Pré-processamento: removendo stopwords


Muitos algoritmos de Processamento de Linguagem Natural utilizam técnicas que podem sofrer muitas interferências por conta dos ruídos. Em algoritmos estatísticos que utilizam a frequência como indicador os ruídos aparecem como palavras que aparecem muitas vezes, porém não tem significado expressivo para o contexto do texto. 

Existem diversas técnicas para remoção destas palavras que recebem o nome de "stopwords". O grande problema destas técnicas é garantir que as palavras removidas realmente são palavras que não são necessárias para o processamento. Vale ressaltar também que alguns algoritmos de PLN utilizam o texto como um todo para extrair relacionamentos entre os termos. 

Veja o exemplo:

Eu sou bonito.

Eu - Sujeito
Sou - Verbo
Bonito - Adjetivo.


Esta estrutura de Sujeito - Verbo - Adjetivo claramente indica que o sujeito da frase tem a característica indicada como adjetivo. Estas heurísticas linguísticas podem ser utilizadas, no entanto a remoção de stopwords descartará as palavras "eu" visto que ela é um pronome. Sendo assim, é interessante que o desenvolvedor entenda como utilizar o processamento para benefício do seu software. Caso isto não seja feito com cuidado este tratamento pode causar distorções no resultado final.


Dois métodos para remover stopwords.

1 - Utilizando listas de palavras

O primeiro método para remoção de stopwords é a utilização de uma lista de palavras. Podemos pré-definir uma lista na qual contenha todas as palavras que deverão ser removidas. Este método é claramente bastante frágil, visto que remoções indevidas podem ser feitas.

Veja o exemplo:

Eu gosto muito de bolo de chocolate.

Lista de stopwords: de, Eu;

Resultado final: gosto muito bolo chocolate. 


Veja um exemplo desta implementação em Java:


import Util.LoadFiles;
import Util.Paths;
import Util.StringUtils;
import java.util.ArrayList;

public class ListOfWordsStopwordsRemover{

public ArrayList<String> removeStopwords(
                         String contentToRemoveStopwords) {
        
        ArrayList<String> words = 
        StringUtils.stringToArrayOfWords(contentToRemoveStopwords);

        ArrayList<String> stopWordsList = 
        LoadFiles.loadSingleFile("Path To your list");
        ArrayList<String> retorno = new ArrayList<>();

        for (String s : words) {
            if (!stopWordsList.contains(s.toLowerCase())) {
                retorno.add(s);
            }
        }

        return retorno;
    }


public static ArrayList<String> loadSingleFile(String path) {
        
        ArrayList<String> lista = new ArrayList<>();
        BufferedReader br = null;

        String sCurrentLine;
        try {
            br = new BufferedReader(new FileReader(path));

            while ((sCurrentLine = br.readLine()) != null) {
                lista.add(sCurrentLine);

            }
        } catch (Exception ex) {
        Logger.getLogger(
        LoadFiles.class.getName()).log(Level.SEVERE, null, ex);
        }
        try {
            if (br != null) {
                br.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        ArrayList<String> retorno = new ArrayList<>();

        for (String s : lista) {
            String[] v = s.split(" ");
            for (int i = 0; i < v.length; i++) {
                if (!v[i].equals("") 
                    || !v[i].equals(" ") || v[i] != null) {
                    retorno.add(v[i]);
                }
            }
        }

        return retorno;

    }

}


2 - Utilizando um Part-of-speech Tagger


É possível utilizar uma técnica linguística para remover palavras que tem classificações que não interessam ao resultado final. O primeiro passo é utilizar um Tagger para marcar a função de cada palavra em um texto. Em seguida o usuário deve selecionar quais tipos de palavras ele gostaria de excluir e quais ele gostaria de incluir. 

Veja o exemplo:

Documento 01 - [PRN]Eu [VRB] sou [ADJ]lindo
Documento 02 - [S]Maria [VRB]tem [PRP]um [S]carro

Tags que iremos aceitar no conjunto final de palavras: Verbos [VB]

Resultado final: Sou, Tem;

O maior problema desta técnica é classificar cada palavra de acordo com sua função na sentença. Visto que cada língua possui suas regras e que o desenvolvimento de um Tagger é bastante complexo. No entanto, algumas universidades desenvolveram ferramentas que realizam esta tarefa e disponibilizam o código para uso da comunidade. Uma delas é a universidade de Stanford nos Estados Unidos que disponibilizou o Stanford Parser. 

Toda sua documentação e download pode ser acessada neste link.

O exemplo a seguir é baseado no Stanford Parser e precisa de suas bibliotecas parra funcionar: 

import Exceptions.NoTagsAvaliableException;
import Model.Phrase;
import Modules.PreProcessing.Interfaces.StopwordsRemover;
import edu.stanford.nlp.simple.Document;
import edu.stanford.nlp.simple.Sentence;
import edu.stanford.nlp.trees.Tree;
import java.util.ArrayList;

public class PosTaggerStopwordRemover{
    
    private ArrayList<String> tags;

    
    public PosTaggerStopwordRemover() {
        this.tags = new ArrayList<>();
    }

    
    public PosTaggerStopwordRemover(ArrayList<String> tags) {
        this.tags = tags;
    }
    
    public String removeStopwords(String contentToRemoveStopwords) {
        if(tags == null || tags.isEmpty()){
            throw new NoTagsAvaliableException();
        }

        String result = "";
        Phrase phrase = new Phrase();
        Document doc = new Document(contentToRemoveStopwords);
        for (Sentence sent : doc.sentences()) {
            Tree t = sent.parse();
            phrase.setOriginalPhrase(t);
            phrase.setWordsExtracted(extractWords(t, tags));
            //System.out.println(phrase);
            for (Tree word : phrase.getWordsExtracted()) {
                int ini = word.toString().indexOf(" ") + 1;
                int fin = word.toString().indexOf(")");
                if (ini > 0 && fin > 0) {
                 result += word.toString().substring(ini,fin) + " ";
                }
            }
        }
        return result;
    }

    private ArrayList<Tree> extractWords(
                            Tree node, ArrayList<String> tags) {

        ArrayList<Tree> stopwordsList = new ArrayList<>();

        for (Tree subtree : node) {
            if ((tags).contains(subtree.label().value())) {
                stopwordsList.add(subtree);
            }
        }

        return stopwordsList;
    }

    public ArrayList<String> getTags() {
        return tags;
    }

    public void setTags(ArrayList<String> tags) {
        this.tags = tags;
    }

}


Outro passo importante é entender o Enum com as tags:

/*
CC Coordinating conjunction
CD Cardinal number
DT Determiner
EX Existential there
FW Foreign word
IN Preposition or subordinating conjunction
JJ Adjective
JJR Adjective, comparative
JJS Adjective, superlative
LS List item marker
MD Modal
NN Noun, singular or mass
NNS Noun, plural
NNP Proper noun, singular
NNPS Proper noun, plural
PDT Predeterminer
POS Possessive ending
PRP Personal pronoun
PRP$ Possessive pronoun
RB Adverb
RBR Adverb, comparative
RBS Adverb, superlative
RP Particle
SYM Symbol
TO to
UH Interjection
VB Verb, base form
VBD Verb, past tense
VBG Verb, gerund or present participle
VBN Verb, past participle
VBP Verb, non­3rd person singular present
VBZ Verb, 3rd person singular present
WDT Wh­determiner
WP Wh­pronoun
WP$ Possessive wh­pronoun
WRB Wh­adverb
 */
public enum PartOfSpeechTagSelectionEnum {

    Coordinating_conjunction("CC"),
    Cardinal_number("CD"),
    Determiner("DT"),
    Existential_there("EX"),
    Foreign_word("FW"),
    Preposition_or_subordinating_conjunction("IN"),
    Adjective("JJ"),
    Adjective_Comparative("JJR"),
    Adjective_superlative("JJS"),
    List_item_marker("LS"),
    Modal("MD"),
    Noun_Singular_or_Mass("NN"),
    Noun_plural("NNS"),
    Proper_noun_singular("NNP"),
    Proper_noun_plural("NNPS"),
    Predeterminer("PDT"),
    Possessive_ending("POS"),
    Personal_pronoun("PRP"),
    Possessive_pronoun("PRP$"),
    Adverb("RB"),
    Adverb_comparative("RBR"),
    Adverb_superlative("RBS"),
    Particle("RP"),
    Symbol("SYM"),
    to("TO"),
    Interjection("UH"),
    Verb_base_form("VB"),
    Verb_past_tense("VBD"),
    Verb_gerund_or_present_participle("VBG"),
    Verb_past_participle("VBN"),
    Verb_non_3rd_person_singular_present("VBP"),
    Verb_3rd_person_singular_present("VBZ"),
    Wh_determiner("WDT"),
    Wh_pronoun("WP"),
    Possessive_wh_pronoun("WP$"),
    Wh_adverb("WRB");

    private String tag;

    PartOfSpeechTagSelectionEnum(String tag) {
        this.tag = tag;
    }

    public String getTag() {
        return tag;
    }
}


As demais classes relacionadas utilizadas neste extrato de código podem ser consultadas no nosso github.

Clique na imagem abaixo para acessar







[Algoritmos] [PLN#5] Pré-processamento: removendo stopwords [Algoritmos] [PLN#5] Pré-processamento: removendo stopwords Reviewed by Vinicius dos Santos on 08:39:00 Rating: 5

Nenhum comentário

Escreve ai sua opinião!