Naive Bayes

Introdução

Neste tutorial iremos apresentar a implentação do algoritmo Naive Bayes usando aplicado a dados numéricos. Utilizaremos neste tutorial o conjunto de dador denominado Pima Inidians Diabetes, utilizado para predizer início de diabetes veja neste link.

Este problema é composto por 768 observações de detalhes médicos de pacientes indianas. Os registros descrevem as medidas instantâneas tomadas do paciente, como sua idade, o número de vezes grávidas e o tratamento do sangue. Todos os pacientes são mulheres com idade igual ou superior a 21 anos. Todos os atributos são numéricos, e suas unidades variam de atributo a atributo.

Cada registro tem um valor de classe que indica se o paciente sofreu um início de diabetes dentro de 5 anos de quando as medidas foram tomadas (1) ou não (0).

Este é um conjunto de dados padrão que tem sido estudado muito na literatura de aprendizagem de máquinas. Uma boa precisão de predição é de 70% a 76%.

Passos do Tutorial

  1. Tratar Dados: carregar os dados do arquivo CSV e divida-o em treinamento e teste conjuntos de dados.
  2. Resumir dados: resumir as propriedades no conjunto de dados de treinamento para que possamos calcular probabilidades e fazer previsões.
  3. Faça uma Previsão: usar os resumos do conjunto de dados para gerar uma única previsão.
  4. Faça previsões: gerar previsões, dado um conjunto de dados de teste e um conjunto de dados de treinamento resumido.
  5. Avalie a precisão: avaliar a precisão das previsões feitas para um conjunto de dados de teste como a porcentagem correta de todas as previsões feitas.

1. Tratar Dados

1.1 Carregar arquivo

A primeira coisa que precisamos fazer é carregar nosso arquivo de dados. Os dados estão no formato CSV sem linha de cabeçalho. Podemos abrir o arquivo com a função open e ler as linhas de dados usando a função de leitor no módulo csv.

Também precisamos converter os atributos que foram carregados como strings em números para que possamos trabalhar com eles. Abaixo está a função loadCsv () para carregar o conjunto de dados Pima indians.


In [13]:
import csv
 
def loadCsv(filename):
    lines = csv.reader(open(filename, "r"))
    dataset = list(lines)
    for i in range(len(dataset)):
        dataset[i] = [float(x) for x in dataset[i]]
    return dataset

Exercicio 1

Teste esta função carregando o dataset pima-indians-diabetes.data e imprime o número de instancias carregadas da seguinte forma "Arquivo carregado pima-indians-diabetes.data com XXX linhas"


In [14]:
### COLOQUE SUA RESPOSTA AQUI

1.2 Dividir Arquivo

Em seguida, precisamos dividir os dados em um conjunto de dados de treinamento, o qual possa ser usado pelo Naive Bayes para fazer previsões e um conjunto de dados de teste para que possamos usar para avaliar a precisão do modelo. Precisamos dividir o conjunto de dados aleatoriamente em treino e teste, em conjuntos de dados com uma proporção de 67% de treinamento e 33% de teste (esta é uma razão comum para testar um algoritmo em um conjunto de dados).

Abaixo está a função splitDataset () que dividirá um determinado conjunto de dados em uma proporção de divisão determinada.


In [15]:
import random
def splitDataset(dataset, splitRatio):
    trainSize = int(len(dataset) * splitRatio)
    trainSet = []
    copy = list(dataset)
    while len(trainSet) < trainSize:
        index = random.randrange(len(copy))
        trainSet.append(copy.pop(index))
    return [trainSet, copy]

Exercicio 2

Teste esta função definindo um dataset mockado com 5 instancias, divida este arquivo em trainamento e teste. Imprima os conjuntos de treinamento e teste gerados, por exemplo, imprimindo:

"Dividiu arquivo com 5 linhas em arquivo de treino com [[2], [5], [4]] e de teste com [[1], [3]]"


In [16]:
### COLOQUE SUA RESPOSTA AQUI

2. Sumarizar Dados

O modelo do Naive Bayes é composto basicamente pela sumarização do conjunto de dados de treinamento. Este sumário é então usado ao fazer previsões.

O resumo dos dados de treinamento coletados envolve a média e o desvio padrão para cada atributo, pelo valor da classe. Por exemplo, se houver dois valores de classe e 7 atributos numéricos, então precisamos de um desvio padrão e médio para cada combinação de atributo (7) e valor de classe (2), ou seja, 14 resumos de atributos. Estes são necessários ao fazer previsões para calcular a probabilidade de valores de atributos específicos pertencentes a cada valor de classe.

Para sumarizar os dados criamos as seguintes subtarefas:

  1. Separar dados por classe
  2. Calcular Média
  3. Calcular o desvio padrão
  4. Conjunto de dados de resumo
  5. Resumir atributos por classe
  6. Separar dados por classe

2.1 Separar dados por classe

A primeira tarefa é separar as instâncias do conjunto de dados de treinamento pelo valor da classe para que possamos calcular as estatísticas para cada classe. Podemos fazer isso criando um mapa de cada valor de classe para uma lista de instâncias que pertencem a essa classe e classificar todo o conjunto de dados de instâncias nas listas apropriadas.

A função separadaByClass () abaixo faz isso.


In [17]:
def separateByClass(dataset):
    separated = {}
    for i in range(len(dataset)):
        vector = dataset[i]
        if (vector[-1] not in separated):
            separated[vector[-1]] = []
        separated[vector[-1]].append(vector)
    return separated

Exercicio 3

Teste este função com alguns exemplos de dados sintéticos e imprima as classes separadas com seus respectivas instancias. Perceba no exemplo acima que a classe se refere ao último elemento do vetor. Segue um exemplo de saída:

"Instancias separadas por classes: {1: [[1, 20, 1], [3, 22, 1]], 0: [[2, 21, 0]]}"


In [18]:
### COLOQUE SUA RESPOSTA AQUI

2.2 Calcular Média e Desvio Padrão

Precisamos calcular a média de cada atributo para um valor de classe. A média é a tendência central central ou central dos dados, e vamos usá-lo como meio de nossa distribuição gaussiana ao calcular probabilidades.

Também precisamos calcular o desvio padrão de cada atributo para um valor de classe. O desvio padrão descreve a variação da disseminação dos dados, e vamos usá-lo para caracterizar a propagação esperada de cada atributo em nossa distribuição gaussiana ao calcular probabilidades.

O desvio padrão é calculado como a raiz quadrada da variância. A variância é calculada como a média das diferenças quadradas para cada valor de atributo da média. Observe que estamos usando o método N-1, que subtrai 1 do número de valores de atributo ao calcular a variância.


In [19]:
import math
def mean(numbers):
    return sum(numbers)/float(len(numbers))
 
def stdev(numbers):
    avg = mean(numbers)
    variance = sum([pow(x-avg,2) for x in numbers])/float(len(numbers)-1)
    return math.sqrt(variance)

Exercicio 4

Crie alguns dados fictícios e teste as funções criadas.Exemplo:

"Cálculo de [1, 2, 3, 4, 5]: média=3.0, stdev=1.5811388300841898"


In [20]:
### COLOQUE SUA RESPOSTA AQUI

2.2 Sumarizar os dados

Agora temos as ferramentas para resumir um conjunto de dados. Para uma determinada lista de instâncias (para um valor de classe), podemos calcular a média e o desvio padrão para cada atributo.

A função zip agrupa os valores de cada atributo em nossas instâncias de dados em suas próprias listas para que possamos calcular os valores de desvio padrão e média para o atributo.


In [21]:
def summarize(dataset):
    summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)]
    del summaries[-1]
    return summaries

Exercicio 5

Crie alguns dados fictícios e teste as funções criadas.Exemplo de saída:

"Sumário dos atributos: [(2.0, 1.0), (21.0, 1.0)]"


In [22]:
### COLOQUE SUA RESPOSTA AQUI

2.3 Sumarizar Atributos por classes

Podemos juntar tudo ao separar nosso conjunto de dados de treinamento em instâncias agrupadas por classe, usando a função summarizeByClass()


In [23]:
def summarizeByClass(dataset):
    separated = separateByClass(dataset)
    summaries = {}
    for classValue, instances in separated.items():
        summaries[classValue] = summarize(instances)
    return summaries

Exercicio 6

Teste a função acima, usando um pequeno conjunto de dados. Exemplo de saída:

Resumo por classe: {1: [(2.0, 1.4142135623730951), (21.0, 1.4142135623730951)], 0: [(3.0, 1.4142135623730951), (21.5, 0.7071067811865476)]}


In [24]:
### COLOQUE SUA RESPOSTA AQUI

3. Realizar as Predição

Agora estamos prontos para fazer previsões usando os resumos preparados a partir dos nossos dados de treinamento. As previsões envolvem o cálculo da probabilidade de uma dada instância de dados pertencer a cada classe e a seleção da classe com a maior probabilidade de previsão.

Podemos dividir essa parte nas seguintes tarefas:
  1. Calcular a função de densidade de probabilidade gaussiana
  2. Calcular probabilidades das classes
  3. Fazer uma previsão
  4. Fazer várias previsões
  5. Obter acurácia

3.1 Calcular a função de densidade de probabilidade gaussiana

Podemos usar uma função gaussiana para estimar a probabilidade de um determinado valor de atributo, dada a média conhecida e o desvio padrão para o atributo estimado a partir dos dados de treinamento.

Dado que os resumos de atributos para cada atributo e valor de classe, o resultado é a probabilidade condicional de um determinado valor de atributo dado um valor de classe.

Veja as referências para os detalhes desta equação para a função de densidade de probabilidade gaussiana. Em resumo, estamos conectando nossos detalhes conhecidos ao Gauss (valor do atributo, média e desvio padrão) e recuperando a probabilidade de que nosso valor de atributo pertença à classe.

Na função calcularProbability (), calculamos o expoente primeiro, depois calculamos a divisão principal. Isso nos permite ajustar a equação bem em duas linhas.


In [25]:
def calculateProbability(x, mean, stdev):
    exponent = math.exp(-(math.pow(x-mean,2)/(2*math.pow(stdev,2))))
    return (1 / (math.sqrt(2*math.pi) * math.pow(stdev, 2))) * exponent

Exercicio 7

Teste a função acima, usando um pequeno conjunto de dados. Exemplo de saída:

"Probabilidade para pertencer a esta classe: 0.06248965759370005"


In [26]:
### COLOQUE SUA RESPOSTA AQUI

3.2 Calcular probabilidades das classes

Neste momento, podemos calcular a probabilidade de um atributo pertencente a uma classe, podemos combinar as probabilidades de todos os valores dos atributos para uma instância de dados e apresentar uma probabilidade de toda a instância de dados pertencente à classe.

Combinamos as probabilidades juntas, multiplicando-as. Na função calculateClassProbabilities () abaixo, a probabilidade de uma determinada instância de dados é calculada multiplicando as probabilidades de atributo para cada classe. O resultado é um mapa de valores de classe para probabilidades.


In [27]:
def calculateClassProbabilities(summaries, inputVector):
    probabilities = {}
    for classValue, classSummaries in summaries.items():
        probabilities[classValue] = 1
        for i in range(len(classSummaries)):
            mean, stdev = classSummaries[i]
            x = inputVector[i]
            probabilities[classValue] *= calculateProbability(x, mean, stdev)
    return probabilities

Exercicio 8

Teste a função acima, usando um pequeno conjunto de dados. Exemplo de saída:

"Probabilidades para cada classe: {0: 0.7820853879509118, 1: 6.298736258150442e-05}"


In [28]:
### COLOQUE SUA RESPOSTA AQUI

3.3 Fazer a Predição

Agora podemos calcular a probabilidade de uma instância de dados pertencente a cada valor de classe, podemos procurar a maior probabilidade e retornar a classe associada.

A função predict() realiza esta tarefa.


In [29]:
def predict(summaries, inputVector):
    probabilities = calculateClassProbabilities(summaries, inputVector)
    bestLabel, bestProb = None, -1
    for classValue, probability in probabilities.items():
        if bestLabel is None or probability > bestProb:
            bestProb = probability
            bestLabel = classValue
    return bestLabel

Exercicio 9

Teste a função acima, usando um pequeno conjunto de dados. Exemplo de saída:

"Entrada {'A': [(1, 0.5)], 'B': [(20, 5.0)]} Consulta [1.1, '?'] Predição: Classe A"


In [30]:
### COLOQUE SUA RESPOSTA AQUI

3.4 Fazer várias predições

Finalmente, podemos estimar a precisão do modelo fazendo previsões para cada instância de dados em nosso conjunto de dados de teste. A função getPredictions () realizará esta tarefa e retornará uma lista de previsões para cada instância de teste.


In [31]:
def getPredictions(summaries, testSet):
    predictions = []
    for i in range(len(testSet)):
        result = predict(summaries, testSet[i])
        predictions.append(result)
    return predictions

Exercicio 10

Teste a função acima, usando um pequeno conjunto de dados. Exemplo de saída:

"Predições: Sumarios {'A': [(1, 0.5)], 'B': [(20, 5.0)]} Teste [[1.1, '?'], [19.1, '?']] Classes Preditas['A', 'B']"


In [32]:
### COLOQUE SUA RESPOSTA AQUI

3.5 Calcular Acurácia

As previsões podem ser comparadas com os valores de classe no conjunto de dados de teste. A acurácia da classificação pode ser calculada como uma relação de precisão entre 0 e 100%. A função getAccuracy () calculará essa relação de precisão.


In [33]:
def getAccuracy(testSet, predictions):
    correct = 0
    for i in range(len(testSet)):
        if testSet[i][-1] == predictions[i]:
            correct += 1
    return (correct/float(len(testSet))) * 100.0

Exercicio 11

Teste a função acima, usando um pequeno conjunto de dados. Exemplo de saída:

"Resultado: Teste [[1, 1, 1, 'a'], [2, 2, 2, 'a'], [3, 3, 3, 'b']] predições ['a', 'a', 'a'] Acurácia 66.66666666666666"


In [34]:
### COLOQUE SUA RESPOSTA AQUI

Exercicio 12

Junte todo o código acima, crie uma função main e execute a predição para o dataset "pima-indians-diabetes.data", observando a acurácia obtida. Execute várias vezes e analise a variação da acurária ao longo dessas execuções.


In [36]:
### COLOQUE SUA RESPOSTA AQUI

In [ ]: