Nesse post vamos detalhar o que são esses dois algoritmos (TF e TF-IDF) e como eles podem ser usados para construir aplicações de PLN bastante úteis. É bastante comum quando utilizamos o Processamento de Linguagem Natural (PLN) termos de escolher quais são as abordagens que iremos utilizar. Atualmente existem três caminhos disponíveis. O primeiro deles é utilizar algoritmos estatísticos; O segundo é a utilização de algoritmos linguísticos. Por fim, podemos buscar aproveitar o melhor de dois mundos e utilizar abordagens híbridas.
Gosta de aprender sobre processamento de linguagem natural? Clique aqui para ver mais.
Este tema já foi previamente discutido neste post. Vale a pena ler, caso você ainda não está familiarizado com este assunto. A abordagem utilizada no algoritmo de Term Frequency (TF) e Term Frequency – Inverse Document Frequency (TF-IDF) é estatística.
De forma geral os dois algoritmos estão conectados pois o TF-IDF é uma extensão do TF.
OBS: Existem vários frameworks de PLN que já implementam esses algoritmos e ajudam você a usá-los. Vale a pena procurar tanto em java quanto em python.
Term Frequency (TF)
A tradução do nome deste algoritmo já nos dá uma dica do que ele trata. A “frequência dos termos” é o processo de encontrar quantas vezes um termo foi repetido em uma sentença.
Veja um exemplo bastante simples:
Linda é linda.
Nesta sentença podemos afirmar que:
- – Existem 3 de termos nesta sentença;
- – A frequência da palavra “Linda” é de 2/3;
- – A frequência da palavra “é” é de 1/3;
Veja este algoritmo implementado na linguagem JAVA:
import Model.Word;
import Util.StringUtils;
import java.util.ArrayList;
import java.util.LinkedHashSet;
public class TermFrequency {
//Gera um ArrayList de palavras
public ArrayList<Word> getTermFrequency(String textToParse) {
ArrayList<String> words =
stringToArrayOfWords(textToParse);
ArrayList<Word> retorno = new ArrayList<>();
//Popula o array
for (String s : words) {
double count = 0;
for (String i : words) {
if (s.equals(i)) {
count++;
}
}
if (!s.isEmpty()) {
double a = count/words.size();
retorno.add(new Word(s,count/words.size()));
}
}
//Remove palavras repetidas usando HashSet
LinkedHashSet<Word> temp = new LinkedHashSet<>();
temp.addAll(retorno);
retorno.clear();
retorno.addAll(temp);
return retorno;
}
//AUXILIAR METHODS
public static ArrayList<String> stringToArrayOfWords(String contentToConvert) {
ArrayList<String> words = new ArrayList<>();
if (contentToConvert != null) {
for (String s : contentToConvert.split(" ")) {
words.add(s);
}
}
return words;
}
}
//Classe palavras
public class Word{
private String word;
private double frequency;
public Word(String word, double frequency) {
this.word = word;
this.frequency = frequency;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public double getFrequency() {
return frequency;
}
public void setFrequency(double frequency) {
this.frequency = frequency;
}
}
Term Frequency – Inverse Document Frequency (TF-IDF)
Este algoritmo utiliza o mesmo conceito de frequência de termos, porém ele trabalha com uma adaptação visando melhorar o seu desempenho. Esta modificação foi pensada para tratar as palavras existentes no corpus analisado que não significam nada para o conjunto. Veja o exemplo abaixo:
- Documento 1: Eu sou homem.
- Documento 2: Eu sou médico.
- Documento 3: Como um bom médico, eu fiz vários cursos de especialização.
Se utilizarmos o algoritmo de TF (sem pré-processamento) podemos perceber que é possível que uma palavra ocorra em todos os documentos. Ex: “Eu”. Ao contrário disso também é possível, ou seja, é perfeitamente possível que uma palavra só ocorra em um documento. Ex: “fiz”.
Ao analisar estes fatos é possível concluir que estes dois extremos são ruins para classificação de documentos. Se uma palavra ocorre em todos eles, não é possível dizer que aquela palavra é um diferencial. O mesmo podemos dizer se a palavra ocorre pouquíssimas vezes. Para isso o algoritmo utiliza uma razão inversa para calcular a frequência destes termos. Observe o algoritmo: TF * IDF
TF(t) = (Número de vezes que t aparece no documento) / (Número total de termos no documento).
IDF(t) = log_e(Número total de termos do documento/ Número de documentos que possuem o termo t).
Desta forma, tendem a se aproximar de zero, todos os termos que se repetem em todos os documentos ou aqueles que aparecem em apenas um documento.
Veja o algoritmo implementado em JAVA:
import Model.Word;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
public class TermFrequencyIdf {
public ArrayList<Word>
getTermFrequencyIdf(
ArrayList<ArrayList<String>> docs,
String textToParse) {
ArrayList<String> wordsToParse =
stringToArrayOfWords(textToParse);
ArrayList<Word> retorno = new ArrayList<>();
//Contabiliza os termos
for (String s : wordsToParse) {
double count = 0;
retorno.add(new Word(s,tfIdf(wordsToParse, docs, s)));
}
//Remove termos repetidos
LinkedHashSet<Word> temp = new LinkedHashSet<>();
temp.addAll(retorno);
retorno.clear();
retorno.addAll(temp);
return retorno;
}
public double tf(ArrayList<String> doc, String term) {
double result = 0;
for (String word : doc) {
if (term.equalsIgnoreCase(word)) {
result++;
}
}
return result / doc.size();
}
//Faz a contagem de acordo com a relação inversa dos documentos
public double idf(ArrayList<ArrayList<String>> docs, String term) {
double n = 0;
for (List<String> doc : docs) {
for (String word : doc) {
if (term.equalsIgnoreCase(word)) {
n++;
break;
}
}
}
return Math.log(docs.size() / n);
}
//Retorna o valor do TF-IDF
public double tfIdf(
ArrayList<String> doc,
ArrayList<ArrayList<String>> docs,
String term) {
return tf(doc, term) * idf(docs, term);
}
}
Estes dois algoritmos são muito interessantes, porém é importante que para utiliza-los você tenha estudado um pouco de Pre-processamento. Visto que existem muitas palavras que são consideradas “lixo” por serem não significativas. Estes ruídos podem prejudicar a aplicação dos algoritmos descritos acima.