Introdução1

Na teoria da probabilidade e na estatística, o teorema de Bayes descreve a probabilidade de um evento com base no conhecimento prévio de condições que possam estar relacionadas ao evento. Por exemplo, se uma determinada condição de saúde está relacionada à idade, então, usando o teorema de Bayes, a idade de uma pessoa pode ser usada para avaliar com mais precisão a probabilidade de que essa condição esteja presente do que pode ser feito sem o conhecimento da idade da pessoa.

O teorema de Bayes foi introduzido pelo Reverendo Thomas Bayes, como um ensaio para resolver um problema na doutrina das chances em 1763, quando se utilizou, pela primeira vez, a probabilidade condicional para fornecer um algoritmo que emprega evidências anteriores para se calcular limites em um parâmetro estatístico desconhecido.

Neste trabalho, explora-se o teorema de Bayes em detalhes, juntamente com suas aplicações, incluindo um exemplo de classificadores Naive Bayes.


Uma pequena revisão de probabilidade (para análise de dados)

1. Experimentos

Para muitas pessoas, uma das primeiras imagens que vêm à mente quando se ouve a palavra experimento é um laboratório químico cheio de tubos de ensaio e equipamentos. O conceito de um experimento na teoria da probabilidade é realmente bastante semelhante: Um experimento é uma operação planejada realizada em condições controladas.

Jogar uma moeda, lançar um dado e escolher uma carta do baralho são exemplos de experimentos.

2. Espaço amostral

O desfecho de um experimento é chamado de um resultado. O conjunto de todos os resultados possíveis de um evento é chamado de espaço amostral, normalmente designado pela letra grega \(\Omega\). Por exemplo, se o nosso experimento é lançar um dado e registrar a face que resultou virada para cima, então o espaço amostral é:

(s <- c(1:6))
## [1] 1 2 3 4 5 6

E o lançamento de uma moeda:

(m <- c("Cara", "Coroa"))
## [1] "Cara"  "Coroa"

3. Evento

Um evento é um subconjunto dos resultados possíveis (ou seja, um subconjunto do espaço amostral) de um experimento.

Figura 1 - Evento

Figura 1 - Evento

No caso do lançamento de um dado, podemos definir alguns eventos:
- P = um número par foi obtido = {2, 4, 6}
- M = um número maior do que 4 foi obtido = {5, 6}

As probabilidades desses eventos podem ser calculadas como:
- P(P) = número de resultados favoráveis / número total de resultados possíveis = 3/6 = 0,5
- P(M) = 2/6 = 0,333

As operações básicas da teoria dos conjuntos, união e interseção de eventos, são possíveis, porque um evento é um conjunto.

Figura 2 - Operação de conjuntos

Figura 2 - Operação de conjuntos

Então, \(P \cup M = \{2, 4, 5, 6\}\) e \(P \cap M = \{6\}\).

Agora, consideremos o evento I = um número ímpar foi obtido. I = {1, 3, 5}.

Então, \(P \cap I = \{ \varnothing \}\), o conjunto vazio.

Esses eventos são chamados eventos mutuamente exclusivos ou disjuntos, porque somente um deles pode ocorrer em um determinado experimento.

Figura 3 - Conjuntos mutuamente exclusivos ou disjuntos

Figura 3 - Conjuntos mutuamente exclusivos ou disjuntos

4. Variável aleatória

Uma variável aleatória é, na realidade, uma função matemática que transforma resultados (de eventos aleatórios) em números, com cada valor tendo alguma probabilidade de ocorrência (que pode ser zero). Formalmente, é uma função de valor real definida no (domínio do) espaço amostral de um experimento.

Figura 4 - Variável aleatória

Figura 4 - Variável aleatória

A partir do exemplo da Figura 4, definimos uma variável aleatória X no espaço amostral do experimento de lançamento de um dado. uma determinada função hash é utilzada para transformar os eventos P e M (ver a definição dos eventos acima) em números. No caso, 1 e 2, respectivamente. A cada valor de X está associada uma probabilidade de ocorrência:
- P(X = 1) = 0,5
- P(X = 2) = 0,33.

5. Eventos coletivamente exaustivos

Um conjunto de eventos é dito ser coletivamente exaustivo se apenas um dos eventos pode ocorrer a qualquer momento. Assim, dois eventos A e B são coletivamente exaustivos se \(\small A \cup B = \Omega\), o espaço amostral, e \(\small A \cap B = \{ \varnothing \}\), o conjunto vazio.

6. Eventos independentes

Se a ocorrência de um evento não tem qualquer efeito sobre a ocorrência de outro, então os dois eventos são independentes. Matematicamente, dois eventos A e B são ditos independentes se:

\[P(A \cap B) = P(AB) = P(A) \times P(B)\]

7. Probabilidade condicional

Considere que uma carta é retirada de um baralho completo. Qual é a probabilidade de que seja uma carta preta?

Fácil! A probabilidade é 50%, pois metade das cartas é preta e metade é vermelha. No entanto, e se nós sabemos apenas que era um carta preta. Qual seria a probabilidade de que fosse um Rei?

A abordagem a esta questão não é tão simples.

Aqui precisamos tratar o conceito de probabilidade condicional. A probabilidade condicional é definida como a probabilidade de um evento A, uma vez que outro evento B já ocorreu. Isso é representado por P(A|B) e pode ser definida como:

\[P(A|B) = \frac {P(A \cap B)}{P(B)}\]

Se o evento A representa escolher um Rei e o evento B escolher uma carta preta, então encontramos P(A|B) usando a fórmula acima:
P(A ∩ B) = P(escolher um rei preto) = 2/52
P(B) = P(escolher uma carta preta) = 1/2
Assim, P(A|B) = 4/52.

8. Probabilidade marginal

A probabilidade marginal é a probabilidade de um evento A ocorrer, independentemente de qualquer outro evento B, ou seja, deixando o evento B à margem do problema.

A probabilidade marginal de A é

\[P(A) = P(A|B) \times P(B) + P(A | \neg B) * P(\neg B)\]

onde \(\small \neg B\) representa o evento onde B não ocorre. Este é um exemplo da Lei da Probabilidade Total.

P(A) pode ser escrita como:

\(\small P(A) = P(A \cap B) + P(A \cap \neg B)\), usando a fórmula para a probabilidade condicional.

Figura 5 - Probabilidade condicional

Figura 5 - Probabilidade condicional

Exemplo

Das pessoas que vão tirar o visto no consulado americano, 70% desejam o visto de turismo e 30% o visto de trabalho. Sabe-se também que 60% das pessoas que desejam o visto de turismo já estiveram nos EUA e, dos que desejam o visto de trabalho, 35% já estiveram nos EUA.

Pedido

Determine a probabilidade de se escolher aleatoriamente uma pessoa que deseje tirar o visto de turismo e nunca esteve nos EUA.

Solução

\[\small P(A \cap B) = P(A|B) \times P(B)\]
A = nunca esteve nos EUA (nunca)
B = deseja visto de turismo (turismo)

P(nunca e turismo) = P(nunca | turismo) x P(turismo) = 0,40 x 0,70 = 0,28

Outro exemplo

Considere uma fábrica que recebe remessas de peças de dois diferentes fornecedores \(A_1\) e \(A_2\). Atualmente, 65% das peças compradas pela empresa são do fornecedor \(A_1\) e os restantes 35% do fornecedor \(A_2\). Portanto, se a peça fosse atribuída aleatoriamente teríamos as probabilidades iniciais (a priori):

\[P(A_1) = 0,65 \ e \ P(A_2) = 0,35\]

A definição formal do teorema de Bayes.

Considere que A e B são quaisquer dois eventos de um espaço amostral, onde \(\small P(B) \ne 0\). Então:

\[\small P(A|B) = \frac {P(A∩B)} {P(B)}\]

Da mesma forma, \[\small P(B|A) = \frac {P(A∩B)} {P(A)}\]

Segue-se que \[\small P(A∩B) = P (A|B) \times P(B) = P(B|A) \times P(A)\]

Assim,

Figura 6 - Teorema de Bayes

Figura 6 - Teorema de Bayes

Este é o teorema de Bayes.

P(A) e P(B) são as probabilidades de se observar A e B independentemente umas das outras. É por isso que podemos dizer que eles são probabilidades marginais. P(B|A) e P(A|B) são probabilidades condicionais.

P(A|B) é chamada de probabilidade posterior, P(B|A) é a verossimilhança, P(A) é a probabilidade anterior da classe e P(B) é a probabilidade anterior do preditor, ou evidência, e pode ser calculado como segue:

\[\small P(B) = P(B|A) \times P(A) + P(B | \neg A) \times P(\neg A)\]


Um exemplo

Em média, uma criança em mil nascida de jovens casais (35 anos ou menos) apresenta a Síndrome de Down. O teste conhecido com triplo filtro detecta a síndrome com 86% de probabilidade. A taxa de falsos positivos é de 5%.

Uma gestante preocupada faz o teste e recebe um resultado positivo. Quais são as chances de que seu bebê tenha síndrome de Down?

Solução

\(\small \neg A\) significa que o evento A não ocorre.

D = Síndrome real
P = Resultado positivo

\(\small P(D) = 1/1000\)
\(\small P(P | D) = 86 \%\)
\(\small P(\neg D) = 999/1000\)
\(\small P(P | \neg D) = 5 \%\)

\[P(D | P) = \frac {P(P | D) \; P(D)} {P(P | D) \; P(D) + P(P | \neg D) \; P(\neg D)} = 0,0169 = 1,69 \%\]

A probabilidade é de 1,69% (surpreendente?).


Um problema a ser resolvido

A avaliação geológica de um campo de petróleo indica que a probabilidade de ele produzir petróleo é de 25%. Existe uma chance de 80% de um determinado poço de prospecção atingir petróleo, dado que exista petróleo no  campo prospectado.

  1. Um poço sob estudo é perfurado e descobre-se que ele está seco. Qual a probabilidade (atualizada) do campo prospectado produzir petróleo?
  2. Caso dois poços sejam perfurados e descobertos secos, qual a (nova) probabilidade do campo prospectado produzir petróleo?
  3. A empresa gostaria de continuar prospectando, contanto que as chances de encontrar petróleo sejam superiores a 1%. Quantos poços precisam ser perfurados antes que o campo seja abandonado?

Naive Bayes (Bayes ingênuo)

Esta é uma técnica de classificação baseada no teorema de Bayes onde se supõe (inocentemente) que os preditores do modelo são independentes. Em termos simples, um classificador Naive Bayes pressupõe que a presença de um determinado atributo em uma classe não está relacionada à presença de qualquer outro atributo

Por exemplo, uma fruta pode ser considerada uma maçã se for vermelha, redonda e tiver um diâmetro de aproximadamente 7,5cm. Mesmo que estas características, ou atributos, dependam uns dos outros ou existam outros atributos, um classificador Naive Bayes considera que todas essas propriedades contribuem de forma independente para o cálculo da probabilidade de que esta fruta seja uma maçã.

O modelo é fácil de se construir e particularmente útil para conjuntos de dados muito grandes. Além de sua simplicidade, Naive Bayes é conhecido por superar até mesmo métodos de classificação altamente sofisticados.


Um exemplo simples

A Figura 7 apresenta um conjunto simplificado de dados que relacionam o clima à ocorrência ou não de Jogo. A questão aqui é se haverá jogo ou não com base nas condições meteorológicas. Esse é um típico problema de classificação e podemos resolvê-lo a partir das seguintes etapas:

Etapa 1: converter o conjunto de dados em uma tabela de frequências

Etapa 2: criar uma tabela de verossimilhança, encontrando as probabilidades condicionais e marginais. Por exemplo, a chance de estar nublado é 6/20, a probabilidade de haver jogo (Sim) é 12/20 e a probabilidade condicional de estar nublado dado que houve jogo é 5/12.

Etapa 3: utilizar o teorema de Bayes para calcular a probabilidade posterior para cada classe. A classe com a maior probabilidade posterior é o resultado da predição.

Problema: Haverá jogo se o tempo estiver ensolarado. Esta afirmação está correta?

Há 83% de chance de haver jogo.

Figura 7 - Vai haver jogo?

Figura 7 - Vai haver jogo?


Outro exemplo - usando o R
Um algoritmo de classificação para filtrar spam em mensagens SMS da telefonia celular

Adaptado livremente de “Machine Learning with R”, Brett Lanz, Packt.

Os dados foram adaptados de SMS Spam Collection.

Este conjunto de dados, sms_spam.csv, inclui o texto de mensagens SMS juntamente com um rótulo que indica se a mensagem é indesejada. As mensagens indesejadas são rotuladas como spam, enquanto mensagens legítimas são marcadas como ham.

library(readr)
sms_raw <- read_csv("sms_spam.csv")
str(sms_raw)
## spec_tbl_df [5,559 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ type: chr [1:5559] "ham" "ham" "ham" "spam" ...
##  $ text: chr [1:5559] "Hope you are having a good week. Just checking in" "K..give back my thanks." "Am also doing in cbe only. But have to pay." "complimentary 4 STAR Ibiza Holiday or £10,000 cash needs your URGENT collection. 09066364349 NOW from Landline "| __truncated__ ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   type = col_character(),
##   ..   text = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>
sms_raw$type <- factor(sms_raw$type) # converte o tipo (ham/spam) para fator

options(digits = 2)
prop.table(table(sms_raw$type)) * 100 # O percentual de spam e ham nas mensagens
## 
##  ham spam 
##   87   13

Preparação dos dados

Utiliza-se o pacote tm criado por Ingo Feinerer para preparar os dados para o processamento. Para detalhes de utilização, consulte o link acima ou use o comando print(vignette("tm"))

A função Corpus() cria um objeto R que armazena um documento de texto. A função tm_map() provê um método para para transformar um corpus e “limpá-lo” dos caracteres que não fazem parte do texto principal das mensagens, como números, preposições, pontuações etc.

library(tm)
## Carregando pacotes exigidos: NLP
sms_corpus <- Corpus(VectorSource(sms_raw$text))
print(sms_corpus)
## <<SimpleCorpus>>
## Metadata:  corpus specific: 1, document level (indexed): 0
## Content:  documents: 5559
inspect(sms_corpus[1:3])
## <<SimpleCorpus>>
## Metadata:  corpus specific: 1, document level (indexed): 0
## Content:  documents: 3
## 
## [1] Hope you are having a good week. Just checking in
## [2] K..give back my thanks.                          
## [3] Am also doing in cbe only. But have to pay.
corpus_limpo <- tm_map(sms_corpus, tolower)
corpus_limpo <- tm_map(corpus_limpo, removeNumbers)
corpus_limpo <- tm_map(corpus_limpo, removeWords, stopwords())
corpus_limpo <- tm_map(corpus_limpo, removePunctuation)
corpus_limpo <- tm_map(corpus_limpo, stripWhitespace)

A função DocumentTermMatrix() cria uma estrutura de dados denominada matriz esparsa a partir de um corpus, onde as linhas da matriz indicam documentos (ou seja, mensagens SMS) e as colunas indicam termos (ou seja, palavras). Cada célula na matriz armazena um número indicando uma contagem das vezes que a palavra indicada pela coluna aparece no documento indicado pela linha.

sms_dtm <- DocumentTermMatrix(corpus_limpo)

Separação dos dados de treinamento e teste

sms_raw_treinamento <- sms_raw[1:4169,]
sms_raw_teste  <- sms_raw[4170:5559,]

sms_dtm_treinamento <- sms_dtm[1:4169,]
sms_dtm_teste  <- sms_dtm[4170:5559,]

sms_corpus_treinamento <- corpus_limpo[1:4169]
sms_corpus_teste  <- corpus_limpo[4170:5559]

options(digits = 2)
prop.table(table(sms_raw_treinamento$type)) * 100
## 
##  ham spam 
##   86   14
prop.table(table(sms_raw_teste$type)) * 100
## 
##  ham spam 
##   87   13

Como pode ser notado, as proporções entre spam e ham são praticamente idênticas nos dois conjuntos de dados.

Word clouds

Uma word cloud (nuvem de palavras) é uma forma bastante comum de descrever visualmente a frequência com que as palavra aparecem em um texto. A nuvem é composta de palavras espalhadas aleatoriamente. As palavras que aparecem com mais frequência no texto são mostradas em uma fonte maior, enquanto termos menos comuns são mostrados em fontes menores. Esse tipo de figura cresceu em popularidade recentemente, uma vez que fornece uma maneira de observar temas de tendências em sítios de mídia social e em discursos.

O pacote wordcloud foi escrito por Ian Fellows. Maiores informações podem ser obtidas aqui.

library(wordcloud)
## Carregando pacotes exigidos: RColorBrewer

A Figura 8 apresenta a nuvem de todos as palavras.

wordcloud(sms_corpus_treinamento, min.freq = 40, random.order = FALSE)
Figura 8 - Word cloud

Figura 8 - Word cloud

Vamos comparar os textos classificados como spam e ham. Iniciemos com os spams.

spam <- subset(sms_raw_treinamento, type == "spam")
wordcloud(spam$text, max.words = 40, scale = c(4, 0.5))
Figura 9 - Word cloud - spam

Figura 9 - Word cloud - spam

Agora, os textos classificados como ham.

ham <- subset(sms_raw_treinamento, type == "ham")
wordcloud(ham$text, max.words = 40, scale = c(4, 0.5))
Figura 10 - Word cloud - ham

Figura 10 - Word cloud - ham

A etapa final no processo de preparação de dados é transformar a matriz esparsa em uma estrutura de dados que pode ser utilizada para treinar o classificador Naive Bayes. Atualmente, a matriz esparsa inclui mais de 7.000 atributos, um para cada palavra que aparece em pelo menos uma mensagem SMS. É improvável que todos estes sejam úteis para a classificação. Para reduzir o número de atributos (palavras), elimina-se as palavras que aparecem em menos de cinco mensagens SMS, ou seja, menos de cerca de 0,1% dos dados utilzados para o treinamento do modelo.

meus_termos <- findFreqTerms(sms_dtm_treinamento, 5)
sms_treinamento <- DocumentTermMatrix(sms_corpus_treinamento, list(dictionary = meus_termos))
sms_teste  <- DocumentTermMatrix(sms_corpus_teste, list(dictionary = meus_termos))

O classificador Naive Bayes normalmente é treinado em dados com atributos categóricos. Isso representa um problema, pois as células na matriz esparsa indicam uma contagem numérica das vezes que uma palavra aparece. Devemos mudar isso para um fator que simplesmente indique Sim ou não, dependendo se a palavra aparece ou não no texto.

O código a seguir define uma função converte_contagens() para converter contagens em fatores e aplica a função a cada coluna das matrizes.

O resultado serão duas matrizes, cada uma com colunas de tipo fator indicando Sim se a palavra de cada coluna aparece nas mensagens que compõem as linhas e Não em caso contrário.

converte_contagens <- function(contagem) {
    contagem <- ifelse(contagem > 0, 1, 0)
    contagem <- factor(contagem, levels = c(0, 1), labels = c("Nao", "Sim"))
    return(contagem)
}
sms_treinamento <- apply(sms_treinamento, MARGIN = 2, converte_contagens)
sms_teste  <- apply(sms_teste, MARGIN = 2, converte_contagens)

Treinamento do modelo

O algoritmo Naive Bayes está implementado em, pelo menos, dois pacotes em R: e1071 e klaR. Pode-se utilizar qualquer um deles. Aqui utiliza-se o primeiro, e1071. O modelo utiliza a presença ou a ausência de palavras para estimar a probabilidade de que uma dada mensagem é spam.

library(e1071)

sms_classificador <- naiveBayes(sms_treinamento, sms_raw_treinamento$type)
sms_teste_pred <- predict(sms_classificador, sms_teste)

Para comparar os valores previstos com os valores reais, usaremos a função CrossTable() no pacote gmodels.

library(gmodels)
CrossTable(sms_teste_pred, sms_raw_teste$type,
           prop.chisq = FALSE, prop.t = FALSE, 
           dnn = c('predito', 'real'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |           N / Row Total |
## |           N / Col Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1390 
## 
##  
##              | real 
##      predito |       ham |      spam | Row Total | 
## -------------|-----------|-----------|-----------|
##          ham |      1203 |        32 |      1235 | 
##              |     0.974 |     0.026 |     0.888 | 
##              |     0.997 |     0.175 |           | 
## -------------|-----------|-----------|-----------|
##         spam |         4 |       151 |       155 | 
##              |     0.026 |     0.974 |     0.112 | 
##              |     0.003 |     0.825 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      1207 |       183 |      1390 | 
##              |     0.868 |     0.132 |           | 
## -------------|-----------|-----------|-----------|
## 
## 

Olhando para a tabela, nota-se que 4 de 1207 mensagens ham (0,3%) foram classificadas incorretamente como spam, enquanto 32 de 183 mensagens spam (17,5%) foram classificadas incorretamente como ham. Um bom resultado para um classificador tão simples.

O modelo possui um parâmetro (laplace) que evita que palavras que apareçam em “zero ham” ou em “zero spam” tenham uma influência exacerbada no processo de classificação. Por exemplo, se a palavra “holiday” aparecer apenas em mensagens spam, isso não significa necessariamente que toda mensagem que inclua esta palavra deva ser classificada como spam.

O código a seguir utiliza o parâmtro laplace = 1:

require(e1071)
sms_classificador2 <- naiveBayes(sms_treinamento, sms_raw_treinamento$type,
                              laplace = 1)
sms_teste_pred2 <- predict(sms_classificador2, sms_teste)
CrossTable(sms_teste_pred2, sms_raw_teste$type,
           prop.chisq = FALSE, prop.t = FALSE,
           dnn = c('predito', 'real'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |           N / Row Total |
## |           N / Col Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1390 
## 
##  
##              | real 
##      predito |       ham |      spam | Row Total | 
## -------------|-----------|-----------|-----------|
##          ham |      1191 |        19 |      1210 | 
##              |     0.984 |     0.016 |     0.871 | 
##              |     0.987 |     0.104 |           | 
## -------------|-----------|-----------|-----------|
##         spam |        16 |       164 |       180 | 
##              |     0.089 |     0.911 |     0.129 | 
##              |     0.013 |     0.896 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      1207 |       183 |      1390 | 
##              |     0.868 |     0.132 |           | 
## -------------|-----------|-----------|-----------|
## 
## 

Uma pequena melhoria pode ser observada neste caso. Entretanto, muitas vezes o resultado melhora significativamente com esta prática.


  1. Parte do conteúdo deste trabalho está baseado em texto publicado por KHYATI MAHENDRU, em ANALYTICS VIDHYA, em 13 de junho de 2019↩︎