Implemente um classifador Naive Bayes para o problema de predizer a qualidade de um carro. Para este fim, utilizaremos um conjunto de dados referente a qualidade de carros, disponível no UCI. Este dataset de carros possui as seguintes features e classe:
Attributos
Classes
In [72]:
import csv
import math
import random
from collections import defaultdict
# retorna uma função que tenta converter uma string
# em int
# se não conseguir, retorna o valor associado à string
# no map not_int_dict
def try_int(not_int_dict):
def _try_int(x):
try:
return int(x)
except:
return not_int_dict[x]
return _try_int
# lista de funções de mapeamento dos valores string em numérico
map_classe_into_int = {'vgood': 4, 'good': 3, 'acc': 2, 'unacc': 1}
map_int_into_classe = {v:k for k, v in map_classe_into_int.items()}
preprocess_list = [
lambda x: {'vhigh': 4, 'high': 3, 'med': 2, 'low': 1}[x],
lambda x: {'vhigh': 4, 'high': 3, 'med': 2, 'low': 1}[x],
try_int({'5more': 6}),
try_int({'more': 5}),
lambda x: {'big': 3, 'med': 2, 'small': 1}[x],
lambda x: {'high': 3, 'med': 2, 'low': 1}[x],
lambda x: map_classe_into_int[x]
]
# converte uma linha contendo valores string em valores numéricos
def preprocess_row(row):
return [f(v) for f, v in zip(preprocess_list, row)]
def loadCsv(filename):
rows = csv.reader(open(filename, "r"))
ds = [preprocess_row(row) for row in rows]
return ds
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]
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
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)
def summarize(dataset):
summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)]
del summaries[-1]
return summaries
def summarizeByClass(dataset):
separated = separateByClass(dataset)
summaries = {}
for classValue, instances in separated.items():
summaries[classValue] = summarize(instances)
return summaries
def calculateProbability(x, mean, stdev):
if stdev == 0:
return 1. if x == mean else .1
exponent = math.exp(-(x-mean)**2/(2*stdev**2))
return (1 / ((2*math.pi) * stdev ** 2)**.5) * exponent
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
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, bestProb
def getPredictions(summaries, testSet):
predictions = []
probs = []
for i in range(len(testSet)):
result, prob = predict(summaries, testSet[i])
predictions.append(result)
probs.append(prob)
return predictions, probs
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
def train_and_test(filepath):
accs = []
ds = loadCsv(filepath)
for i in range(100):
train, test = splitDataset(ds, splitRatio=.67)
summaries = summarizeByClass(train)
predictions, _ = getPredictions(summaries, test)
accs.append(getAccuracy(test, predictions))
return accs
accs = train_and_test("carData.csv")
import pandas as pd
pd.Series(accs).describe().to_frame().T
Out[72]:
In [210]:
# leitura do arquivo
df = pd.read_csv("carData.csv", header=None)
# índice das colunas de features
f_cols = list(range(0, 6))
# frequência de registros por classe
freq_classes = df.groupby(6).size()
# classes
classes = df[6].unique()
# probabilidade atribuída às combinações de feature/classe
# que não possuem exemplos no dataset
DEFAULT_NO_FREQ_PROB = 0.01
def fit(df):
# probabilidades estimadas como #(feature, classe)/#classe
base_probs = {}
# para cada feature
for f_col in f_cols:
# matriz de probabilidades da feature x classes
# agrupa pela classe e pela feature
# manda índice da feature para as colunas
# divide pela frequência das classes
# preenche os missing values(pares não presentes no dataset)
base_probs[f_col] = df.groupby([6, f_col]).size()\
.unstack() \
.div(freq_classes, axis=0)\
.fillna(DEFAULT_NO_FREQ_PROB)
# função que realiza a predição sobre um dataset
# a partir das probabilidades estimadas no passo anterior
def predict(ds):
predictions = []
probs = []
# para cada exemplo
for ix, row in ds.iterrows():
prob = {}
# para cada classe
for classe in classes:
# calcula a probabilidade do exemplo estar associado à classe
prob[classe] = 1.
# como o produto das probabilidades de cada uma de suas features pertencer à classe
for f_col in f_cols:
prob[classe] = prob[classe] * base_probs[f_col].loc[classe, str(row[f_col])]
predictions.append(max(prob.keys(), key=lambda key: prob[key]))
probs.append(prob)
return probs, predictions
return predict
In [199]:
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
accs = []
for x in range(100):
# particiona o dataset em treino(77%) e test(33%)
df_train, df_test = train_test_split(df, test_size=.33)
# treina
nb = fit(df_train)
# prediz
_, predictions = nb(df_test)
# calcula acurácia
acc = accuracy_score(df_test[6], predictions)
accs.append(acc)
pd.Series(accs).describe().to_frame().T
Out[199]:
Crie uma versão de sua implementação usando as funções disponíveis na biblioteca SciKitLearn para o Naive Bayes (veja aqui)
In [200]:
from sklearn.naive_bayes import GaussianNB
import numpy as np
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import confusion_matrix
In [201]:
ds = loadCsv("carData.csv")
df = pd.DataFrame(ds, columns=range(7))
X = df[list(range(6))]
y = df[6]
In [248]:
accs = []
for x in range(100):
# particiona dataset em treino(77%) e teste(33%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.33)
nb = GaussianNB()
# prediz
nb.fit(X_train, y_train)
# calcula acurácia
acc = accuracy_score(y_test, nb.predict(X_test))
accs.append(acc)
pd.Series(accs).describe().to_frame().T
Out[248]:
Comparando os dois últimos modelos
In [233]:
ix_train, ix_test = train_test_split(np.arange(df.shape[0]), test_size=.33, random_state=100)
# modelo 1
# estimativa de probabilidade utilizando a frequência das features
df_train, df_test = df.iloc[ix_train, :], df.iloc[ix_test, :]
nb1 = fit(df_train)
probs_1, predictions_1 = nb1(df_test)
probs_1 = pd.DataFrame(probs_1, index=df_test.index)
# modelo 2
# estimativa de probabilidade utilizando distribuição normal das features
ds = pd.DataFrame(loadCsv("carData.csv"), columns=range(7))
X_train, y_train, X_test, y_test = ds.iloc[ix_train, list(range(6))], ds.iloc[ix_train, 6],\
ds.iloc[ix_test, list(range(6))], ds.iloc[ix_test, 6]
nb2 = GaussianNB().fit(X_train, y_train)
predictions_2 = [map_int_into_classe[p] for p in nb2.predict(X_test)]
probs_2 = nb2.predict_proba(X_test)
probs_2 = pd.DataFrame(probs_2, columns=[map_int_into_classe[v] for v in nb2.classes_], index=df_test.index)
# juntando as predições
predictions = pd.DataFrame({'model_1': predictions_1, 'model_2': predictions_2, 'correct': df_test.loc[:, 6]}, index=df_test.index)
predictions = pd.merge(df_test, predictions, left_index=True, right_index=True)
predictions.loc[:, 'model_1'] = predictions.model_1.astype('category', categories=['unacc', 'acc', 'good', 'vgood'], ordered=True)
predictions.loc[:, 'model_2'] = predictions.model_2.astype('category', categories=['unacc', 'acc', 'good', 'vgood'], ordered=True)
In [234]:
predictions.groupby(['model_1', 'model_2']).size().unstack().fillna(0)
Out[234]:
Analisando um caso em que model_1 classificou como muito bom e model_2 classificou como muito ruim
In [235]:
predictions[(predictions.model_1 == 'vgood') & (predictions.model_2 == 'unacc')].sample()
Out[235]:
In [236]:
probs_1.loc[1662]
Out[236]:
In [237]:
probs_2.loc[1662]
Out[237]:
In [238]:
# ter a feature lug_boot = big parece contribuir bastante para a probabilidade do modelo 1
# dado que a frequência de lug_boot = big na classe vgood é bastante alta(68%)
df_train.groupby([6, 4]).size().unstack().div(df_train.groupby(6).size(), axis=0).sort_values('big', ascending=False)
Out[238]:
Analisando um caso em que model_2 classificou como muito bom e model_1 classificou como muito ruim
In [240]:
predictions[(predictions.model_2 == 'vgood') & (predictions.model_1 == 'unacc')]
Out[240]:
Analisando um caso em que ambos classificaram como muito ruim quando era muito bom
In [242]:
predictions[(predictions.model_2 == 'unacc') & (predictions.model_1 == 'unacc') & (predictions.correct == "vgood")]
Out[242]:
Analisando um caso em que ambos classificaram como muito bom quando era muito ruim
In [244]:
predictions[(predictions.model_2 == 'vgood') & (predictions.model_1 == 'vgood') & (predictions.correct == "unacc")]
Out[244]:
In [246]:
# observar que a frequência de exemplos da classe vgood é bem menor que da classe unacc
df_train.groupby(6).size()
Out[246]: