Naive Bayes - Trabalho

Questão 1

Implemente um classifacor 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

  1. buying: vhigh, high, med, low
  2. maint: vhigh, high, med, low
  3. doors: 2, 3, 4, 5, more
  4. persons: 2, 4, more
  5. lug_boot: small, med, big
  6. safety: low, med, high

Classes

  1. unacc, acc, good, vgood

Questão 2

Crie uma versão de sua implementação usando as funções disponíveis na biblioteca SciKitLearn para o Naive Bayes (veja aqui)

Questão 3

Analise a acurácia dos dois algoritmos e discuta a sua solução.

Questão 1


In [533]:
#Bibliotecas
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split 
from sklearn.metrics import accuracy_score, classification_report

In [534]:
#Lendo o arquivo com os dados
df = pd.read_csv('carData.csv', header=None)
df.columns = ['buying', 'maint','doors','persons','lug_boot','safety','class']
df.head()


Out[534]:
buying maint doors persons lug_boot safety class
0 vhigh vhigh 2 2 small low unacc
1 vhigh vhigh 2 2 small med unacc
2 vhigh vhigh 2 2 small high unacc
3 vhigh vhigh 2 2 med low unacc
4 vhigh vhigh 2 2 med med unacc

In [535]:
#Separa em conjunto de treino (70%) e teste (30%)
df_train, df_test = train_test_split(df,test_size=0.3)

#Faz uma copia dos mesmos dados para usar na questão 2
df_train_sci = df_train.copy()
df_test_sci = df_test.copy()

In [536]:
#separa conjunto de teste em features e classe e calcula o tamanho total de treino
df = df_train
totalSize = len(df)
df_test_X = df_test.iloc[:,0:-1]
df_test_y = df_test['class']

In [537]:
#separa os dados por classe
for class_value in df['class'].unique():
    class_set = df[df['class'] == class_value]

In [538]:
#Calcula a frequencia de cada atributo de cada feature em cada classe
#frequency = [[[[k,len(df[df['class'] == i].index & df[df[j] == k].index)]  for k in df[j].unique()] for j in df.columns[:-1]] for i in df['class'].unique()]
frequency = [[[len(df[df['class'] == i].index & df[df[j] == k].index)  for k in df[j].unique()] for j in df.columns[:-1]] for i in df['class'].unique()]
frequency


Out[538]:
[[[246, 177, 187, 241],
  [189, 246, 213, 203],
  [236, 216, 201, 198],
  [221, 407, 223],
  [326, 277, 248],
  [407, 249, 195]],
 [[51, 86, 60, 61],
  [80, 52, 69, 57],
  [55, 66, 71, 66],
  [125, 0, 133],
  [76, 86, 96],
  [0, 121, 137]],
 [[0, 18, 31, 0],
  [19, 0, 10, 20],
  [4, 10, 17, 18],
  [23, 0, 26],
  [0, 19, 30],
  [0, 0, 49]],
 [[0, 21, 30, 0],
  [15, 0, 0, 36],
  [11, 13, 14, 13],
  [27, 0, 24],
  [14, 18, 19],
  [0, 30, 21]]]

In [539]:
frequency_test = [[[k  for k in df[j].unique()] for j in df.columns[:-1]] for i in df['class'].unique()]

In [540]:
#Monta tabela de indices para enumerar cada atributo
columns = ['buying', 'maint','doors','persons','lug_boot','safety']
matrixA={}
p = 0;
for j in columns:
    matrixA[j] = frequency_test[0][p]
    p += 1
matrixA['class'] = list(df['class'].unique())
frequency_label = pd.DataFrame.from_dict(matrixA, orient='index')
frequency_label


Out[540]:
0 1 2 3
buying vhigh med low high
maint med vhigh high low
doors 2 3 5more 4
persons 4 2 more None
lug_boot small med big None
safety low med high None
class unacc acc vgood good

In [541]:
#(frequency_label.loc['buying'] == 'vhigh').argmax()
#sum(frequency[3][0][:] + frequency[2][0][:] + frequency[1][0][:] + frequency[0][0][:])
#frequency[3][0][:]

In [542]:
#Calcula a probabilidade de cada feature
likelihood_feature = [[(frequency[0][index][((frequency_label.loc[j] == k).argmax())] + frequency[1][index][((frequency_label.loc[j] == k).argmax())] + frequency[2][index][((frequency_label.loc[j] == k).argmax())] + frequency[3][index][((frequency_label.loc[j] == k).argmax())])/totalSize for k in df[j].unique()] for index,j in enumerate(df.columns[:-1])]
likelihood_feature


Out[542]:
[[0.2456575682382134,
  0.24979321753515302,
  0.2547559966914806,
  0.24979321753515302],
 [0.2506203473945409,
  0.24648469809760132,
  0.24152191894127378,
  0.261373035566584],
 [0.2531017369727047,
  0.2522746071133168,
  0.2506203473945409,
  0.24400330851943755],
 [0.32754342431761785, 0.33664185277088504, 0.3358147229114971],
 [0.34408602150537637, 0.3308519437551696, 0.3250620347394541],
 [0.33664185277088504, 0.3308519437551696, 0.3325062034739454]]

In [543]:
#Calcula a probabilidade de cada classe
likelihood_class = [(sum(df['class'] == h))/totalSize for h in df['class'].unique()]
likelihood_class


Out[543]:
[0.70388751033912322,
 0.21339950372208435,
 0.040529363110008272,
 0.042183622828784122]

In [544]:
#Calcula a probabilidade de cada atributo em cada feature em cada classe
frequency_prob = [[[len(df[df['class'] == i].index & df[df[j] == k].index)/sum(df['class'] == i)  for k in df[j].unique()] for j in df.columns[:-1]] for i in df['class'].unique()]
frequency_prob


Out[544]:
[[[0.28907168037602821,
   0.20799059929494712,
   0.21974148061104584,
   0.28319623971797886],
  [0.22209165687426558,
   0.28907168037602821,
   0.25029377203290248,
   0.23854289071680376],
  [0.27732079905992951,
   0.25381903642773207,
   0.23619271445358403,
   0.23266745005875442],
  [0.25969447708578142, 0.47826086956521741, 0.26204465334900118],
  [0.38307873090481787, 0.32549941245593422, 0.29142185663924797],
  [0.47826086956521741, 0.29259694477085779, 0.2291421856639248]],
 [[0.19767441860465115,
   0.33333333333333331,
   0.23255813953488372,
   0.23643410852713179],
  [0.31007751937984496,
   0.20155038759689922,
   0.26744186046511625,
   0.22093023255813954],
  [0.2131782945736434,
   0.2558139534883721,
   0.27519379844961239,
   0.2558139534883721],
  [0.48449612403100772, 0.0, 0.51550387596899228],
  [0.29457364341085274, 0.33333333333333331, 0.37209302325581395],
  [0.0, 0.4689922480620155, 0.53100775193798455]],
 [[0.0, 0.36734693877551022, 0.63265306122448983, 0.0],
  [0.38775510204081631, 0.0, 0.20408163265306123, 0.40816326530612246],
  [0.081632653061224483,
   0.20408163265306123,
   0.34693877551020408,
   0.36734693877551022],
  [0.46938775510204084, 0.0, 0.53061224489795922],
  [0.0, 0.38775510204081631, 0.61224489795918369],
  [0.0, 0.0, 1.0]],
 [[0.0, 0.41176470588235292, 0.58823529411764708, 0.0],
  [0.29411764705882354, 0.0, 0.0, 0.70588235294117652],
  [0.21568627450980393,
   0.25490196078431371,
   0.27450980392156865,
   0.25490196078431371],
  [0.52941176470588236, 0.0, 0.47058823529411764],
  [0.27450980392156865, 0.35294117647058826, 0.37254901960784315],
  [0.0, 0.58823529411764708, 0.41176470588235292]]]

In [545]:
#data = df_test.iloc[0]
#data = data[:-1] #esclui a classe
#df_test.iloc[0]

In [546]:
#feature_index = [(frequency_label.loc[i] == data.loc[i]).argmax() for i in data.index]
#feature_index

In [547]:
#Faz a predição do conjunto de teste
df_test = df_test_X
predicts = ["" for x in range(len(df_test))]
k = 0;
for row in range(len(df_test)):
    data = df_test.iloc[row]
    feature_index = [(frequency_label.loc[h] == data.loc[h]).argmax() for h in data.index]
    
    class_chance = np.zeros(len(likelihood_class))
    for i,prob_class in enumerate(likelihood_class):
        prob = 1;
        for j,feature in enumerate(feature_index):
            prob *= frequency_prob[i][j][feature]
        class_chance[i] = (prob * prob_class) #/ likelihood_feature   
    predicts[k] = [frequency_label.loc['class'][class_chance.argmax()]]  
    k += 1

In [548]:
result_y = np.squeeze(np.asarray(predicts))
correct_values = np.sum(result_y == df_test_y.values)

In [549]:
correct_pct = correct_values / len(df_test_y)
print('Porcentagem de acerto = {0}'.format(correct_pct))


Porcentagem de acerto = 0.8651252408477842

In [550]:
print("\nClassification Report:")
print(classification_report(y_true=df_test_y, y_pred=result_y, target_names=["unacc", "acc", "good", "vgood"]))


Classification Report:
             precision    recall  f1-score   support

      unacc       0.74      0.69      0.72       126
        acc       0.73      0.44      0.55        18
       good       0.91      0.97      0.94       359
      vgood       0.86      0.38      0.52        16

avg / total       0.86      0.87      0.86       519

Questão 2


In [551]:
from sklearn.naive_bayes import MultinomialNB, GaussianNB
from sklearn.preprocessing import LabelEncoder

In [552]:
#Transforma os valores string em númericos
#Na minha versão foi feito o tratamento de todos os valores sem precisar converter, porém
#a função do scikit não é adaptado para valores strings
for i in range(0, df_train_sci.shape[1]):
    df_train_sci.iloc[:,i] = LabelEncoder().fit_transform(df_train_sci.iloc[:,i])
    
for i in range(0, df_test_sci.shape[1]):
    df_test_sci.iloc[:,i] = LabelEncoder().fit_transform(df_test_sci.iloc[:,i])

#Separa em features e classes
train_X_sci =  df_train_sci.iloc[:,:-1]
test_X_sci =  df_test_sci.iloc[:,:-1]
train_y_sci = df_train_sci.iloc[:,-1]
test_y_sci = df_test_sci.iloc[:,-1]

In [553]:
#Faz o fit e o predict com a implementação do scikit
nb = GaussianNB()
nb.fit(train_X_sci, train_y_sci)
result_y_sci = nb.predict(test_X_sci)
correct_pct_sci = accuracy_score(test_y_sci, result_y_sci)

In [554]:
print('Porcentagem de acerto = {0}'.format(correct_pct_sci))


Porcentagem de acerto = 0.6088631984585742

In [555]:
print("\nClassification Report:")
print(classification_report(y_true=test_y_sci, y_pred=result_y_sci, target_names=["unacc", "acc", "good", "vgood"]))


Classification Report:
             precision    recall  f1-score   support

      unacc       0.48      0.09      0.15       126
        acc       0.00      0.00      0.00        18
       good       0.84      0.81      0.82       359
      vgood       0.11      1.00      0.19        16

avg / total       0.70      0.61      0.61       519

C:\Users\Carlos\Miniconda3\envs\carlos\lib\site-packages\sklearn\metrics\classification.py:1135: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples.
  'precision', 'predicted', average, warn_for)

Questão 3


In [556]:
print('Resultado no meu algoritmo: {0}'.format(correct_pct))
print('Resultado no algoritmo do sklearn: {0}'.format(correct_pct_sci))


Resultado no meu algoritmo: 0.8651252408477842
Resultado no algoritmo do sklearn: 0.6088631984585742

Na primeira etapa são somadas as quantidades de ocorrências de cada atributo. Em seguida é criado um mapa de índices para ser possível de identificar os valores de probabilidades armazenados, de acordo com os labels string. Então é calculado a probabilidade de cada feature e posteriormente a probabilidade de cada classe, e a probabilidade de cada atributo que foi calculado na etapa inicial.

Com todas as probabilidades a etapa de predição consiste, basicamente, em fazer o seguinte cálculo de probabilidades, já armazenadas.

$$ v_{NB} = argmax(P(v_{j}) \prod_{i} P(A_{i}|v_{j})) $$

Algumas observações podem ser feitas sobre resultados. O algoritmo do sklearn apresentou um desempenho abaixo do algoritmo implementado. O resultado dos dois algoritmos para o mesmo conjunto de dados, podem ser observados acima. Isso pode ter acontecido devido a transformação dos dados em numéricos para o algoritmo do sklearn, podendo ter sido realizado alguma medida, em que os dados foram mal ponderados pelos valores assumidos. Já no algoritmo implementado, no qual as probabilidades foram calculadas corretamente, e sem alterar os valores das features, o resultado sempre ficou próximo dos 85%. Considerando que um resultado razoável para esta base é de 70% a 76%, o resultado ficou bem acima do esperado.

É possível observar que as classes 'vgood' e 'acc' tiveram uma taxa de acerto baixa, principalmente no algoritmo do sklearn, isso deve ter acontecido devido ao desbalanceamento entre as classes, e pelo algoritmo utilizar apenas dados de probabilidades. Algumas classes podem apresentar valores de características bem representativos e já outras nem tanto. Fazendo com que as predições caiam nas classes bem representadas.