Documente Academic
Documente Profesional
Documente Cultură
ICMC-USP
9 de agosto de 2017
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Capítulo
1
Instalação do Python e bibliotecas
$ which python3
$ which pip3
ou
2
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Após digitar esses comandos, toda vez que você quiser executar algum script,
basta digitar o comando python3 script.py.
Adicionalmente, nos exemplos apresentados neste material e para os trabalhos
aplicados distribuídos no decorrer da disciplina, serão necessárias quatro bibliotecas am-
plamente utilizadas para relizar experimentos de mineração e ciência de dados:
• NumPy1 ,
• SciPy2
• scikit-learn3
• matplotlib4
3
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Capítulo
2
Iniciando o estudo e exploração de dados
Esta parte do material vai ilustrar como a linguagem Python pode ser utilizada
para uma análise exploratória de um conjunto de dados, de forma a encontrar padrões
nos dados e extrair conhecimento desses dados. Existem duas maneiras de explorar um
conjunto de dados: por meio de técnicas estatísticas, mais especificamente da estatística
descritiva, e de técnicas de visualização.
Nesta parte do material, serão apresentadas algumas das principais medidas esta-
tísticas utilizadas para descrever um conjunto de dados, bem como suas funções corres-
pondentes nas bibliotecas da linguagem Python consideradas, além de técnicas simples
capazes de ilustrar graficamente padrões presentes em um conjunto de dados.
Inicialmente, na Seção 2.1, será apresentada uma breve introdução às operações
matemáticas básicas para a manipulação de dados com a biblioteca NumPy. Na Seção
2.2, será discutido como as bases de dados consideradas no presente material estão nor-
malmente estruturadas, e serão apresentadas algumas medidas estatísticas para análise
exploratória de dados. Na Seção 2.3, será discutida a ocorrência de dados multivariados,
que condizem com os cenários estudados em mineração de dados. Na Seção 2.4, serão
demonstrados alguns exemplos simples de gráficos que podem auxiliar em uma análise
inicial dos dados. Por fim, na Seção 2.5, serão propostos alguns exercicios com a finali-
dade de praticar os conceitos apresentados.
2.1.1. Arrays
O principal tipo de dados disponibilizado pela biblioteca NumPy e que será mais utilizado
no decorrer deste material é conhecido como numpy.array. Um array pode ser ins-
tanciado por meio da chamada numpy.array(lista), na qual lista é um objeto
do tipo list contendo apenas valores numéricos. Por exemplo:
3
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import numpy
lista = [1, 2, 3, 4, 5]
x = numpy.array(lista)
print(x)
import numpy
x = numpy.array([1, 2, 3, 4, 5])
y = 2
print(x + y)
print(x - y)
print(y - x)
print(x * y)
print(x / y)
print(y / x)
import numpy
x = numpy.array([1, 2, 3, 4, 5])
y = numpy.array([6, 7, 8, 9, 10])
print(x + y)
print(x - y)
print(y - x)
print(x * y)
1 Na verdade, diversas operações podem ser feitas também com arrays de tamanhos e dimensões distin-
tas. Entretanto, o comportamento da NumPy em tais cenários pode não ser tão intuitivo. Para o leitor inte-
ressado neste tópico, recomenda-se a leitura de: https://scipy.github.io/old-wiki/pages/
EricsBroadcastingDoc.
4
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
print(x / y)
print(y / x)
Ao analisar as saídas do código anterior, pode-se perceber que, quando os operandos são
dois arrays do mesmo tamanho, as operações de soma, subtração, multiplicação e divi-
são ocorrem elemento a elemento. Embora os arrays utilizados nos exemplos anteriores
tenham apenas uma dimensão, as operações podem ser aplicadas a arrays e matrizes com
qualquer número de dimensões.
Por fim, a NumPy fornece ainda várias funções pré-definidas para explorar diver-
sas propriedades de um array qualquer x. Dentre as funções que serão utilizadas ao longo
deste material, pode-se mencionar:
A seguir, serão discutidas algumas das principais medidas estatísticas que podem
ser utilizadas para uma exploração inicial dos dados com a finalidade de extrair informa-
ções relevantes de uma base de dados tal como a Iris.
5
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
2.2.1.1.1 Média
1 n
x̄ = · ∑ xi (1)
n i=1
import numpy
print(media)
print(media)
2.2.1.1.2 Mediana
mediana = numpy.median(x)
print(mediana)
6
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
2.2.1.1.3 Percentis
O intervalo (também conhecido como amplitude) consiste na diferença entre o valor má-
ximo e mínimo de um conjunto de observações. Em Python, o mesmo pode ser calculado
como:
valor_maximo = numpy.max(x)
valor_minimo = numpy.min(x)
intervalo = valor_maximo - valor_minimo
print(intervalo)
em que x̄ consiste na média das observações (Equação 1). Para manter a mesma unidade
dos dados originais, o desvio-padrão é definido como [Magalhães e de Lima 2000]:
√
s = s2 . (3)
media = numpy.mean(x)
n = len(x)
diferencas = x - media
variancia = numpy.sum(diferencas * diferencas) / (n - 1)
desvio_padrao = numpy.sqrt(variancia)
7
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
# seguintes funcoes
variancia = numpy.var(x, ddof=1)
desvio_padrao = numpy.std(x, ddof=1)
2.2.1.3.1 Obliquidade
8
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import numpy
import scipy.stats
2.2.1.3.2 Curtose
A curtose é uma medida que caracteriza o achatamento da distribuição dos dados. Assim
como a obliquidade, os seus valores podem ser negativos, positivos ou próximos de 0.
No primeiro caso, a distribuição é mais achatada e apresenta picos mais baixos e caudas
mais leves4 quando comparada à distribuição normal. No segundo caso, a distribuição dos
dados apresenta picos mais elevados e caudas mais pesadas5 ao se comparar à distribuição
normal. Por fim, no último caso, a distribuição dos dados apresenta achatamento e caudas
próximas ao que ocorre com a distribuição normal.
A equação para o cálculo da curtose é definida como:
import numpy
import scipy.stats
9
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
curtose = scipy.stats.kurtosis(dados)
print(curtose)
import pandas
# carregando iris.csv
dados = pandas.read_csv('iris.csv')
10
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
pyplot.hist(notas, bins='auto')
pyplot.title('Histograma')
11
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
pyplot.ylabel('Frequencia')
pyplot.xlabel('Nota')
pyplot.show()
Figura 2.3: Exemplo de scatter plot para dois atributos da base de dados Iris.
12
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
from matplotlib import pyplot
# carregando iris.csv
dados = pandas.read_csv('iris.csv')
pyplot.show()
Alternativamente, pode-se também gerar uma matriz de scatter plots, que irá con-
ter os scatter plots para todos os pares possíveis de atributos. Ademais, na diagonal de
tal matriz, pode-se apresentar informações a respeito de cada atributo (por exemplo, o seu
histograma). Na Figura 2.4 é apresentada a matriz de scatter plots para a base Iris, com
os histogramas dos atributos contidos na diagonal. Tal matriz pode ser gerada por meio
do seguinte código:
import pandas
from pandas.plotting import scatter_matrix
from matplotlib import pyplot
# carregando iris.csv
dados = pandas.read_csv('iris.csv')
13
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
from pandas.plotting import scatter_matrix
from matplotlib import pyplot
14
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
# carregando iris.csv
dados = pandas.read_csv('iris.csv')
2.5. Exercícios
1. Pesquise e explique com as suas palavras sobre o que extraem as medidas de co-
variância e correlação. Qual a utilidade delas? Aplique-as para a base de dados
Iris, apresente e interprete os resultados (dica: use as funções DataFrame.cov7
e DataFrame.corr8 ).
2. Considere a matriz de scatter plots da Figura 2.4. A partir dos plots, o que pode ser
observado? Existe alguma classe mais separada das demais? Existem classes sobre-
postas? Qual a sua opinião sobre as possíveis implicações das classes sobrepostas
para uma tarefa de mineração de dados?
3. Calcule e apresente, para cada atributo da base Iris, sua obliquidade e sua curtose.
Compare os resultados obtidos com o box plot da Figura 2.5. Ademais, gere os
7 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.
DataFrame.cov.html
8 https://pandas.pydata.org/pandas-docs/stable/generated/pandas.
DataFrame.corr.html
15
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
histogramas para cada atributo da base (dica: pesquise sobre os parâmetros neces-
sários para gerar histogramas com a função DataFrame.plot). Que atributos
possuem distribuições mais simétricas? Há a presença de valores extremos (outli-
ers) para algum atributo? Se sim, qual? O que você acha que pode ser a razão para
a ocorrência de tais valores?
Referências
[Faceli et al. 2011] Faceli, K., Lorena, A. C., Gama, J., e Carvalho, A. C. P. L. F. (2011).
Inteligência artificial: Uma abordagem de aprendizado de máquina. Rio de Janeiro:
LTC, 2:192.
16
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Capítulo
3
Pré-processamento
1 http://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+
%28Diagnostic%29
2 http://archive.ics.uci.edu/ml/datasets/Contraceptive+Method+Choice
18
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
dados = pandas.read_csv('breast_cancer.csv')
3.3. Amostragem
Diversos algoritmos de AM, sejam pela suas complexidades computacionais ou pelas
quantidades de memória exigida, apresentam dificuldades em tratar conjuntos de dados
de tamanho elevado. Uma quantidade de dados muito grande pode tornar o processo de
treinamento demorado. Um meio de contornar essa dificuldade é a utilização de amostras
do conjuntos de dados original. A utilização de um subconjunto menor de objetos, em
geral tornará mais simples e rápida a geração de um modelo.
Entretanto, um ponto importante a ser levado em consideração está relacionado
ao nível ao qual a amostra representa a distribuição original dos dados. Normalmente,
em casos nos quais a mesma não seja representativa, o modelo de AM induzido não será
capaz de atingir uma eficiência aceitável para o problema.
Um modo para tratar o ponto supracitado, resume-se na utilização de técnicas
de amostragem estatística, as quais aumentam as chances de que as amostras extraídas
da base de dados original possam ser informativas e representativas. Duas das técnicas
mais utilizadas são a amostragem simples e a amostragem estratificada. Ambas serão
introduzidas a seguir.
19
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
caso, quando não existe reposição, cada objeto poderá acontecer apenas uma vez em uma
amostra. Assim, a probabilidade de ocorrência de cada objeto é alterada a cada passo do
processo. O código abaixo apresenta um exemplo para cada caso.
import pandas
dados = pandas.read_csv('breast_cancer.csv')
import pandas
dados = pandas.read_csv('breast_cancer.csv')
20
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
for c in classes:
# obtendo os indices do DataFrame
# cujas instancias pertencem a classe c
indices_c = dados['diagnosis'] == c
import pandas
dados = pandas.read_csv('breast_cancer.csv')
for c in classes:
# obtendo os indices do DataFrame
# cujas instancias pertencem a classe c
indices_c = dados['diagnosis'] == c
21
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
22
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
Para o caso de redundância de atributos, tem-se que um deles pode estar forte-
mente correlacionado com outro, o que indica um comportamento similar de suas varia-
ções. Algumas vezes, em tais casos, o valor de um atributo pode ser calculado ou obtido
23
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
3.6. Ruídos
Ruídos normalmente consistem em valores que aparentemente não pertencem à distribui-
ção que gerou o conjunto de dados à disposição [Faceli et al. 2011]. Vários podem ser
os motivos para a ocorrência dos mesmos, desde erros na digitação ao tabular os dados,
problemas de medição em instrumentos utilizados para coletar os dados ou, até mesmo,
valores pouco comuns mas reais. Portanto, ao descartar objetos que apresentem valores
ruidosos para um ou mais atributos, pode-se perder informações relevantes para o pro-
blema estudado.
A fim de reduzir a influência de ruídos nos atributos do conjunto de dados estu-
dado, diversas técnicas podem ser aplicadas. Dentre elas, uma das mais simples consiste
em dividir os valores de cada atributo em faixas, de modo que cada faixa contenha apro-
ximadamente a mesma quantidade de valores. Em seguida, os valores contidos em cada
faixa são substituídos por alguma medida que os sumarize como, por exemplo, a média.
Um exemplo desta técnica é demonstrado a seguir.
import pandas
dados = pandas.read_csv('breast_cancer.csv')
24
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
dados = pandas.read_csv('cmc.csv')
Quando houverem atributos qualitativos ordinais, pode-se também optar por repre-
sentações binárias. Entretanto, as mesmas deverão ser diferentes da representação 1-de-c,
uma vez que as distâncias entre os possíveis valores de um atributo não serão mais iguais
para todos os casos. Para isso, pode-se utilizar o código cinza ou o código termômetro
[Faceli et al. 2011].
O código cinza consiste na transformação dos valores inteiros para as suas respec-
tivas representações binárias. Em Python, tal transformação pode ser realizada conforme
segue.
25
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
dados = pandas.read_csv('cmc.csv')
import pandas
dados = pandas.read_csv('breast_cancer.csv')
26
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
xi j − min j
xi j = (1)
max j − min j
sendo min j e max j , nessa ordem, os valores mínimo e máximo do atributo j para o con-
junto de dados considerado.
Na normalização por padronização, os diferentes atributos contínuos poderão abran-
ger diferentes intervalos, mas deverão possuir os mesmos valores para alguma medida de
posição e de espalhamento/variação [Faceli et al. 2011]. Tipicamente, tais medidas irão
consistir na média e no desvio-padrão. Neste caso, o valor normalizado de um atributo j
em um objeto i é dado por:
xi j − x̄· j
xi j = (2)
sj
onde x̄· j e s j representam a média do atributo j e o seu desvio-padrão, respectivamente.
Desse modo, cada cada atributo j terá média zero e desvio-padrão unitário.
Os dois tipos de normalização supramencionados podem ser executados conforme
o código em seguida.
27
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
from sklearn.preprocessing import minmax_scale
from sklearn.preprocessing import scale
dados = pandas.read_csv('breast_cancer.csv')
28
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
import pandas
from sklearn.decomposition import PCA
dados = pd.read_csv('breast_cancer.csv')
29
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
dados_pca = pd.DataFrame(dados_pca,
columns=['comp1', 'comp2'])
import pandas
dados = pd.read_csv('breast_cancer.csv')
3.9. Exercícios
1. Observe que os códigos apresentados na Seção 3.3.2 nem sempre retornam uma
amostra com o tamanho desejado exato (dica: teste ambos os códigos com a base
Iris, por exemplo). Por que isso acontece? Escreva uma função que receba como
entrada um DataFrame, um tamanho de amostra desejado, o tipo de amostragem
estratificada a ser feita e retorne uma amostra estratificada com o tamanho exato
desejado. Quais as implicações desta nova função no resultado da amostragem?
2. Repare que o código apresentado na Seção 3.4 considera os exemplos de todas
as classes para o cálculo dos valores a serem inseridos no DataFrame. Escreva
3 http://scikit-learn.org/stable/modules/feature_selection.html
30
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
uma função que calcule e insira tais valores separadamente para cada classe. Ade-
mais, tal função deverá receber como parâmetro a medida que será calculada (média
ou mediana). Considerando a base de dados breast_cancer_missing.csv,
crie um arquivo CSV com o novo DataFrame gerado para cada caso.
3. Escreva uma função que receba como entrada um DataFrame, calcule a correla-
ção entre todos os seus atributos (através da função DataFrame.corr) e retorne
um novo DataFrame mantendo apenas um atributo dentre aqueles que possuem
correlação acima (ou abaixo, caso a correlação seja negativa) de um limiar infor-
mado como parâmetro.
5. Repare que no código apresentado na Seção 3.8 o novo DataFrame gerado possui
duas colunas nomeadas ’comp1’ e ’comp2’. Pesquise sobre o PCA e descreva
com as suas palavras o que esses dois novos atributos representam.
Referências
[Abdi e Williams 2010] Abdi, H. e Williams, L. J. (2010). Principal component analysis.
Wiley interdisciplinary reviews: computational statistics, 2(4):433–459.
[Faceli et al. 2011] Faceli, K., Lorena, A. C., Gama, J., e Carvalho, A. C. P. L. F. (2011).
Inteligência artificial: Uma abordagem de aprendizado de máquina. Rio de Janeiro:
LTC, 2:192.
[Garcia et al. 2013] Garcia, S., Luengo, J., Sáez, J. A., Lopez, V., e Herrera, F. (2013).
A survey of discretization techniques: Taxonomy and empirical analysis in supervised
learning. IEEE Transactions on Knowledge and Data Engineering, 25(4):734–750.
[Lim et al. 2000] Lim, T.-S., Loh, W.-Y., e Shih, Y.-S. (2000). A comparison of predic-
tion accuracy, complexity, and training time of thirty-three old and new classification
algorithms. Machine learning, 40(3):203–228.
31
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
[Street et al. 1992] Street, W. N., Wolberg, W. H., e Mangasarian, O. L. (1992). Nuclear
feature extraction for breast tumor diagnosis.
32
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Capítulo
4
Análise de agrupamento de dados
33
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
• C1 ∪C2 ∪ · · · ∪Ck = X;
• Ci 6= 0,
/ ∀i ∈ [1, k]; e
• Ci ∩C j = 0,
/ ∀i, j ∈ [1, k] e i 6= j.
Exercício 4.1. Uma partição rígida, conforme previamente definida, é comumente refe-
renciada na literatura como agrupamento particional exclusivo. Outros tipos de partições
encontradas na literatura são: probabilístico, fuzzy ou particional não exclusivo. No pri-
meiro caso, cada objeto possuirá uma probabilidade de pertencer a cada grupo com a
restrição de que a soma de suas probabilidades é igual a 1. No segundo caso, cada ob-
jeto tem um grau de pertinência no intervalo [0, 1] para cada grupo. Por fim, no terceiro
caso, cada objeto pode ser incluído em mais de um grupo. Escreva, para cada cenário,
as respectivas propriedades, de modo similar ao apresentado anteriormente para o caso
particional exclusivo.
A escolha do valor de p define variações para essa medida. Dentre elas, os três casos mais
conhecidos são [Faceli et al. 2011]:
m
d(xi , x j ) = ∑ |xil − x jl |; (2)
l=1
34
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Por sua vez, dentre as principais medidas de similaridade, pode-se citar a correla-
ção de Pearson e o cosseno entre dois vetores [Faceli et al. 2011].
Para atributos qualitativos, uma das medidas de dissimilaridade mais conhecidas
é a distância de Hamming, definida como:
m
d(xi , x j ) = ∑ I(xil = x jl ), (5)
l=1
sendo I(·) a função indicadora, a qual retorna valor igual a um quando a condição passada
a ela é verdadeira e zero, caso contrário.
Em Python, diversas medidas de distância podem ser calculadas para um conjunto
de dados por meio da função pdist do módulo scipy.spatial.distance1 . Um
exemplo de código é apresentado a seguir.
distance.pdist.html
35
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
• Single-linkage: a distância d(Ci ,C j ) entre dois grupos é dada pela distância mínima
36
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
• Complete-linkage: a distância d(Ci ,C j ) entre dois grupos é dada pela distância má-
xima entre os seus objetos. Ou seja:
• Average-linkage: a distância d(Ci ,C j ) entre dois grupos é dada pela distância média
entre os objetos de diferentes grupos. Ou seja:
1
d(Ci ,C j ) = d(xa , xb ). (8)
|Ci ||C j | xa∑
∈Ci
xb ∈C j
37
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Figura 4.1: Exemplo de dendrograma para um conjunto de dados gerado a partir de três
distribuições normais multivariadas.
38
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
39
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
otimização com inicialização aleatória que garante a convergência para um mínimo local
da Equação (9). O mesmo é apresentado no Algoritmo 2.
Algoritmo 2: k-means.
Entrada: conjunto de dados X ∈ Rn×m e o número de grupos k
Saída : agrupamento particional de X em k grupos
1 gerar k centróides aleatoriamente;
2 repita
3 calcular a distância entre cada objeto x j e cada centróide x̄Ci ;
4 atribuir cada objeto x j ao grupo Ci com centróide mais próximo;
5 recalcular o centróide de cada grupo conforme a Equação (10);
6 até que um critério pré-definido seja atingido ou que os objetos não mudem
de grupo;
40
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
a Discuta quais serão as características dos grupos encontrados por esse algoritmo.
• Objeto central: todo objeto xi contendo uma quantidade de objetos vizinhos, con-
tando ele próprio, maior ou igual a um parâmetro MinPts. Um vizinho é determi-
nado como todo objeto separado por, no máximo, uma distância ε de xi .
• Objeto de borda: todo objeto que não satisfaz as condições para objeto central,
mas que pertence à vizinhança de um objeto central.
• Ruído: todo objeto que não pertence a nenhuma das duas categorias anteriores.
3Éprovado que este novo algoritmo minimiza a distância L1 , também conhecida como distância de
Manhattan.
41
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Figura 4.2: Exemplo de classificação de objetos feita pelo DBSCAN com MinPts = 3
(Fonte: [Chire 2011]4 ).
Algoritmo 3: DBSCAN.
Entrada: conjunto de dados X ∈ Rn×m , um raio ε e a quantidade mínima de
vizinhos para um objeto ser considerado central MinPts
Saída : agrupamento particional de X
1 rotular cada objeto como central, borda ou ruído;
2 remover objetos rotulados como ruído;
3 ligar todos os objetos rotulados como centrais que estejam dentro de um raio
ε uns dos outros;
4 definir cada componente de objetos centrais conectados como um grupo;
5 inserir cada objeto rotulado como borda ao grupo de algum dos seus objetos
centrais associados, resolvendo empates que venham a ocorrer;
42
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Exercício 4.5. Uma das principais desvantagens do DBSCAN decorre de o mesmo não
ser capaz de identificar corretamente um agrupamento quando as densidades variam am-
plamente de grupo para grupo [Tan et al. 2006]. Por que isso acontece? Por que não é
possível contornar tal problema ao aumentar o valor de ε ou alterar o valor de MinPts?
43
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Portanto, valores altos dessa medida indicam soluções que obedecem seu viés. Ademais,
ao generalizar os cálculos de d(Ci ,C j ) e D(Cl ), variantes dessa medida podem ser geradas
[Vendramin et al. 2010]. Dentre elas, 17 são descritas em [Bezdek e Pal 1998].
A implementação do ID não está disponível nas bibliotecas utilizadas neste mate-
rial. Ela será exigida como exercício na Seção 4.7.
( )
1
b(xi ) = min d(xi , x j ) . (16)
1≤h≤k |Ch | x j∑
∈ Ch
h 6= l
Com isso, a LS é definida como a silhueta média entre todos os objetos do conjunto de
dados. Ou seja:
1 n
LS(C) = ∑ S(xi ). (17)
n i=1
44
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
n_clusters=3,
init='random',
n_init=100)
Exercício 4.6.1. Os dois índices relativos apresentados nesta parte não são adequados
para validar o algoritmo DBSCAN. Por que?
Para auxiliar na resposta, aplique o algoritmo DBSCAN no conjunto de dados
moons.csv, fornecido como material suplementar (observe que pode ser necessário
ajustar o valor do parâmetro ε para se obter um bom resultado). Gere um scatter plot para
a base de dados com cores para seus rótulos originais e um scatter plot com cores para
os rótulos encontrados pelo DBSCAN. Calcule o valor o ID e o LS para a solução gerada
pelo DBSCAN. Compare os scatter plots com os valores dos índices.
b O SSE normalmente não é adequado para escolher uma dentre várias soluções com
diferentes números de grupos. Por que? (Dica: aplique o k-means em diversos
conjuntos de dados, por exemplo blobs.csv e Iris, considerando vários valores
para o números de grupos e observe o comportamento do SSE).
45
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
A seguir, cinco dos índices de validação externa mais conhecidos para avaliação
de agrupamentos particionais exclusivos serão apresentados. O respectivo código será
apresentado ao final desta seção.
Esta medida retorna valores no intervalo [0, 1], com valores mais altos indicando
uma maior similaridade entre duas partições.
a11
IJ(Cob ,Cre f ) = . (19)
a11 + a01 + a10
46
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Assim como o IR, o IJ está contido no intervalo [0, 1] com valores mais altos
apontando uma maior concordância entre Cob e Cre f .
ob re f
a − (a+c)(a+b)
a+b+c+d
IRA(C ,C )= (a+c)+(a+b)
. (21)
2 − (a+c)(a+b)
a+b+c+d
re f
|Ciob ∩C j |
P(i, j) = , (23)
n
|Ciob |
Pob (i) = , (24)
n
re f
|C j |
Pre f ( j) = . (25)
n
47
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
O valor mínimo para a IM é igual a zero, sendo que valores mais altos indicam uma
melhor concordância entre as partições comparadas. Uma das principais desvatangens
dessa medida é a ausência de um limitante superior. Para fins comparativos, uma versão
normalizada da mesma para o intervalo [0, 1] é mais adequada [Strehl e Ghosh 2002].
Uma das variações mais conhecidas (IMN) é apresentada na próxima subseção.
IM(Cob ,Cre f )
IMN(Cob ,Cre f ) = p , (26)
H(Cob ) H(Cre f )
Desse modo, a IMN está contida no intervalo [0, 1], com valores mais altos apontando
uma maior similaridade entre as partições comparadas.
4.6.3.6. Exemplos
# Calculando o IRA.
ira = adjusted_rand_score(rotulos_dados, rotulos_kmeans)
48
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
# Calculando o IM.
im = mutual_info_score(rotulos_dados, rotulos_kmeans)
# Calculando o IMN.
imn = normalized_mutual_info_score(rotulos_dados,
rotulos_kmeans)
a Qual é o termo que domina o valor do IR? Por que isso acontece?
4.7. Exercícios
1. Uma extensão direta do algoritmo k-means, capaz de gerar, divisivamente, uma
hierarquia de grupos, é conhecida por bisecting k-means. Tal extensão é descrita na
Seção 8.2.3 do Capítulo 8 do livro de [Tan et al. 2006]5 . Implemente o bisecting
k-means, de modo que retorne a hierarquia de grupos produzida, e aplique-o ao
conjunto de dados Iris.
Referências
[Bezdek e Pal 1998] Bezdek, J. C. e Pal, N. R. (1998). Some new indexes of cluster
validity. IEEE Transactions on Systems, Man, and Cybernetics, Part B (Cybernetics),
28(3):301–315.
[Drineas et al. 2004] Drineas, P., Frieze, A., Kannan, R., Vempala, S., e Vinay, V. (2004).
Clustering large graphs via the singular value decomposition. Machine learning,
56(1):9–33.
[Ester et al. 1996] Ester, M., Kriegel, H.-P., Sander, J., Xu, X., et al. (1996). A density-
based algorithm for discovering clusters in large spatial databases with noise. In Kdd,
volume 96, pages 226–231.
5 Este
capítulo está disponível gratuitamente em http://www-users.cs.umn.edu/~kumar/
dmbook/ch8.pdf.
49
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
[Everitt et al. 2001] Everitt, B., Landau, S., Leese, M., e Stahl, D. (2001). Cluster analy-
sis. 2001. Arnold, London.
[Faceli et al. 2011] Faceli, K., Lorena, A. C., Gama, J., e Carvalho, A. C. P. L. F. (2011).
Inteligência artificial: Uma abordagem de aprendizado de máquina. Rio de Janeiro:
LTC, 2:192.
[Jain 2010] Jain, A. K. (2010). Data clustering: 50 years beyond k-means. Pattern
recognition letters, 31(8):651–666.
[Jain e Dubes 1988] Jain, A. K. e Dubes, R. C. (1988). Algorithms for clustering data.
Prentice-Hall, Inc.
[Tan et al. 2006] Tan, P.-N. et al. (2006). Introduction to data mining. Pearson Education
India.
[Vendramin et al. 2010] Vendramin, L., Campello, R. J., e Hruschka, E. R. (2010). Rela-
tive clustering validity criteria: A comparative overview. Statistical analysis and data
mining: the ASA data science journal, 3(4):209–235.
[Xu e Wunsch 2005] Xu, R. e Wunsch, D. (2005). Survey of clustering algorithms. IEEE
Transactions on neural networks, 16(3):645–678.
50
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Capítulo
5
Classificação e regressão
18
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
seus k vizinhos.
Devido à sua simplicidade, o kNN possui ampla utilização na solução de diver-
sos problemas do mundo real. Os pseudo-códigos para treinamento e teste do kNN são
apresentados, respectivamente, nos Algoritmos 1 e 2.
Algoritmo 1: Treinamento kNN.
Entrada: conjunto de treinamento T = {xi , yi }ni=1 , valor de k e uma medida
de distância d(·, ·)
Saída : classificador kNN
1 armazenar o conjunto de treinamento e o valor de k;
Em Python, pode-se executar o kNN conforme o código que pode ser visto a
seguir.
import pandas
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
19
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
le = LabelEncoder()
y = le.fit_transform(dados['Name'])
# instancia um knn
# n_neighbors indica a quantidade de vizinhos
# metric indica a medida de distancia utilizada
knn = KNeighborsClassifier(n_neighbors=5,
metric='euclidean')
# treina o knn
knn.fit(X_treino, y_treino)
Exercício 5.2.1. Discuta sobre os possíveis problemas que podem ocorrer com o kNN
nos cenários a seguir:
5.3. Exercícios
1. O kNN pode também ser utilizado para problemas de regressão local [Mitchell 1997,
Faceli et al. 2011]. Com a scikit-learn esse tipo de tarefa pode ser realizada por
meio da classe KNeighborsRegressor2 . Escreva um código que treine um
classificador kNN para regressão considerando os requisitos a seguir:
1 Este é um problema, em aprendizado de máquina, conhecido como "maldição da dimensionalidade"
2 http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.
KNeighborsRegressor.html
20
Padilha, V. A. e Carvalho, A. C. P. L. F., Mineração de Dados em Python
Gere um scatter plot para os dados de treinamento utilizando como eixo horizontal
os valores dos exemplos e como eixo vertical os rótulos. Gere um line plot3 para
cada resultado das possíveis combinações entre ponderação e valor de k. Responda:
Referências
[Faceli et al. 2011] Faceli, K., Lorena, A. C., Gama, J., e Carvalho, A. C. P. L. F. (2011).
Inteligência artificial: Uma abordagem de aprendizado de máquina. Rio de Janeiro:
LTC, 2:192.
[Mitchell 1997] Mitchell, T. M. (1997). Machine learning. 1997. Burr Ridge, IL: Mc-
Graw Hill, 45(37):870–877.
3 https://matplotlib.org/users/pyplot_tutorial.html
21
Predictive model selection and evaluation
First of all, we do all necessary imports and load the Breast Cancer Wisconsin Diagnostic dataset.
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
DEFAULT_N_NEIGHBORS = 5
Out[1]:
sample_id
5 rows × 31 columns
In [16]:
le = LabelEncoder()
breast_cancer[labels] = le.fit_transform(breast_cancer[labels])
Then, we write a simple method to train, test and return a kNN classifier, its predicted results, its accuracy score and its 0-1 loss for a
dataset.
In [3]:
To train and test sklearn classifiers, we will need the data as numpy arrays. So, we can extract them using the code below.
In [4]:
np.set_printoptions(precision=4, suppress=True)
X:
[[ 17.99 10.38 122.8 ..., 0.2654 0.4601 0.1189]
[ 20.57 17.77 132.9 ..., 0.186 0.275 0.089 ]
[ 19.69 21.25 130. ..., 0.243 0.3613 0.0876]
...,
[ 16.6 28.08 108.3 ..., 0.1418 0.2218 0.0782]
[ 20.6 29.33 140.1 ..., 0.265 0.4087 0.124 ]
[ 7.76 24.54 47.92 ..., 0. 0.2871 0.0704]]
y:
[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 1 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1
0 1 0 1 1 0 0 0 1 1 0 1 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 0 0 0 1 0 0 1 0 0
0 0 0 0 0 0 1 1 1 0 1 1 0 0 0 1 1 0 1 0 1 1 0 1 1 0 0 1 0 0 1 0 0 0 0 1 0
0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 0 1 1 0 0 0 0 1 0 0 1 1 1 0 1
0 1 0 0 0 1 0 0 1 1 0 1 1 1 1 0 1 1 1 0 1 0 1 0 0 1 0 1 1 1 1 0 0 1 1 0 0
0 1 0 0 0 0 0 1 1 0 0 1 0 0 1 1 0 1 0 0 0 0 1 0 0 0 0 0 1 0 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0
0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 1 1 1 0 0
0 0 1 0 1 0 1 0 0 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1
1 0 1 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0
0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 1 0 0
1 0 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 1 0 0 1 0 1 0 1 1
0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 0]
Simple holdout
The simple holdout method consists of splitting the original dataset in two disjoint subsets:
In [5]:
# Splitting the original dataset.
# The train subset will contain a proportion of 0.66 objects from the original dataset.
# The test subset will contain a proportion of 0.34 objects from the original dataset.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.34, stratify=y, random_state=seed)
# Since the accuracy and 0-1 loss are complementary measures, their sum must be 1.0.
print('Accuracy + 0-1 loss = {}'.format(accuracy + loss))
Accuracy: 0.9226804123711341
0-1 loss: 0.07731958762886593
Accuracy + 0-1 loss = 1.0
K-fold cross-validation
K-fold cross-validation consists of splitting the original dataset in k disjoint subsets of approximately equal size. Then, at each iteration, k − 1
subsets are used as the training set and the remaining subset is used as the test set. In the end, we can calculate the mean accuracy as
the performance measure.
Sklearn provides several different classes to perform k-fold cross-validation. In this example, we use the StratifiedKFold class, since it breaks
the original dataset in k stratified disjoint subsets. So, each subset will maintain the same proportion of objects in each class as in the original
dataset.
In [6]:
splits = 10
skfold = StratifiedKFold(n_splits=splits, random_state=seed)
trained_knns = []
accuracies = []
accuracies = np.array(accuracies)
print('{}-fold cross-validation accuracy mean: {}'.format(splits, np.mean(accuracies)))
print('{}-fold cross-validation accuracy std: {}'.format(splits, np.std(accuracies, ddof=1)))
ROC analysis
For a binary classification problem, where we have a positive ( + ) and a negative ( − ) class, we can obtain the confusion matrix of the
expected and predicted results. The confusion matrix is organized as:
Predicted + Predicted −
Expected + TP FN
Expected − FP TN
True Positives (TP): the number of positive examples that were correctly predicted as positive;
True Negatives (TN): the number of negative examples that were correctly predicted as negative;
False Negatives (FN): the number of positive examples that were wrongly predicted as negative;
False Positives (FP): the number of negative examples that were wrongly predicted as positive.
True Positive Rate (TPR): also known as sensibility. It measures the hit rate for the positive class. It is calculated as:
TP
TPR = ;
TP + FN
False Positive Rate (FPR): also known as specificity. It measures the hit rate for the negative class. It is calculated as:
FP
FPR = .
TN + FP
Many classifiers output scores (or probabilities) when classifying an unseen example. These scores are usually thresholded in order to
return a binary classification.
The ROC analysis consists of using several thresholds for the output scores of a classifier. Then, for each threshold, the respective TPR and
FPR values can be calculated. By plotting the obtained (TPR, FPR) pairs, we obtain the ROC curve.
Finally, a commonly used measure to compare classifiers is the area under the ROC curve (ROC AUC). Such a measure lies between 0 and 1,
with values close to 1 indicating better results.
# Calculatting False Positive Rate and True Positive Rate values for different thresholds.
# The first parameter consists of the expected labels.
# The second parameter consists of the predicted scores for the positive class. In this example
# the positive class is assumed to be the one with label = 1.
false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_prob[:, 1])
Finally, we plot the ROC curve. The diagonal line of such a plot indicates a classifier with random predictions.
In [8]:
# setting linewidth = 2
lw = 2
plt.plot(false_positive_rate,
true_positive_rate,
color='blue',
lw=lw,
label='ROC curve (area = {:.4f})'.format(roc_auc))
plt.show()
Hypothesis testing
Usually, when comparing two classifiers (namely, f1 and f 2 ), the null-hypothesis (H 0 ) states that their performances are equivalent. For this
situation, Demšar (2006) recommends the Wilcoxon signed-rank test.
Next, we present an example extracted from (Demšar, 2006). In such an example, we have the area under the curve (AUC) for the C4.5
algorithm with the parameter m (the minimal number of examples in a leaf) equal to 0 and C4.5 with tunned m (C4.5+m) considering 14
datasets.
In [9]:
Out[9]:
In [10]:
# Running Wilcoxon test. When zero_method='zsplit' the zero ranks are splitted between positive and negative ones.
wilcoxon(c45, c45m, zero_method='zsplit')
Out[10]:
WilcoxonResult(statistic=12.0, pvalue=0.010968496564224731)
The Wilcoxon signed-rank test outputs a p-value close to 0.01. If we consider a significance level (α) of 0.05 we can conclude that C4.5 and
C4.5+m performances are not equivalent.
The Wilcoxon signed-rank test was not designed to compare multiple random variables. So, when comparing multiple classifiers, an
"intuitive" approach would be to apply the Wilcoxon test to all possible pairs. However, when multiple tests are conducted, some of them will
reject the null hypothesis only by chance (Demšar, 2006).
For the comparison of multiple classifiers, Demšar (2006) recommends the Friedman test.
The Friedman test ranks the algorithms from best to worst on each dataset with respect to their performances. Its null-hypothesis (H0 )
states that all algorithms are equivalent and their mean ranks are equal.
Next, we present an example extracted from (Demšar, 2006). In such an example, we have the AUC for four classifiers: C4.5 with m = 0 and
the confidence interval parameter cf = 0.25, C4.5 with tunned m, C4.5 with tunned cf and C4.5 with both parameters tunned.
In [11]:
Out[11]:
In [12]:
Out[12]:
FriedmanchisquareResult(statistic=51.285714285714278, pvalue=1.7912382226666844e-06)
The Friedman test outputs a very small p-value. For many significance levels (α) we can conclude that the performances of all algorithms are
not equivalent.
Considering that the null-hypothesis was rejected, we usually have two scenarios for a post-hoc test (Demšar, 2006):
All classifiers are compared to each other. In this case we apply the Nemenyi post-hoc test.
All classifiers are compared to a control classifier. In this scenario we apply the Bonferroni-Dunn post-hoc test.
To perform both of the aformentioned post-hoc tests, we need the average rank of each algorithm,
In [13]:
# Calculating the ranks of the algorithms for each dataset. The value of p is multipled by -1
# because the rankdata method ranks from the smallest to the greatest performance values.
# Since we are considering AUC as our performance measure, we want larger values to be best ranked.
ranks = np.array([rankdata(-p) for p in performances_array])
Then, we will calculate the critical differences and plot the results of each test (Nemenyi and Bonferroni-Dunn).
In [14]:
# This method computes the critical difference for Nemenyi test with alpha=0.1.
# For some reason, this method only accepts alpha='0.05' or alpha='0.1'.
cd = compute_CD(average_ranks,
n=len(performances),
alpha='0.1',
test='nemenyi')
plt.show()
In [15]:
# This method computes the critical difference for Bonferroni-Dunn test with alpha=0.05.
# For some reason, this method only accepts alpha='0.05' or alpha='0.1'.
cd = compute_CD(average_ranks,
n=len(performances),
alpha='0.05',
test='bonferroni-dunn')
plt.show()
References
Demšar, J. (2006). Statistical comparisons of classifiers over multiple data sets. Journal of Machine learning research, 7, 1-30.
Naive Bayes
Naive Bayes is a very simple but powerful classification method. For a given object x, Naive Bayes calculates x's probability to belong to each
class yi (i = 1, ⋯, k), using the Bayes' theorem:
P(y i) P (x 1 , ⋯, xm | yi)
P (y i | x) = .
P (x1 , ⋯, x m)
Additionally, it assumes that the features are independent from each other (which is the reason why it is called naive):
P(x1 , ⋯, x m | y i) = ∏m
j=1
P (xj | y i).
So, we obtain:
P (y i) ∏ m
j=1
P (x j | y i)
P(yi | x) = .
P (x 1 , ⋯, xm)
For a given object x, Naive Bayes will output the the Maximum a Posteriori (MAP) estimate:
m
P (y) ∏ j = 1 P (x j | y)
ŷ = arg maxy P (y | x) = arg max y .
P (x1 , ⋯, x m)
m
ŷ = arg maxy P (yi) ∏j = 1 P (xj | y).
Practical example
First of all, we do all the necessary imports and load the Mushroom dataset.
In [1]:
import pandas as pd
import matplotlib.pyplot as plt
data = pd.read_csv('data/mushroom.csv')
# We drop the 'stalk-root' feature because it is the only one containing missing values.
data = data.drop('stalk-root', axis=1)
data.head()
Out[1]:
stalk- stalk-
cap- cap- cap- gill- gill- gill- gill- stalk- color- color- veil- veil- ring- ring-
bruises? odor ...
shape surface color attachment spacing size color shape above- below- type color number type
ring ring
0 x s n t p f c n k e ... w w p w o p
1 x s y t a f c b k e ... w w p w o p
2 b s w t l f c b n e ... w w p w o p
3 x y w t p f c n n e ... w w p w o p
4 x s g f n f w b k t ... w w p w o e
5 rows × 22 columns
Unfortunately, scikit-learn does not implement the classical Naive Bayes algorithm which calculates the conditional probabilities P(xj | y i) as the
proportion of objects from class yi that assume each particular categorical value for feature j. However, scikit-learn contains the BernoulliNB
class which assumes that data is distributed according to multivariate Bernoulli distributions.
So, for the Mushroom dataset, we can transform each categorical feature to dummy variables. Note that such a conversion clearly
violates the indepence assumption between features. However, Naive Bayes has been proven to achieve good performance in
several applications where indepence is violated (for example, in text classication).
In [2]:
# Creates a BernoulliNB. binarize=None indicates that there is no need to binarize the input data.
nb = BernoulliNB(binarize=None)
nb.fit(X_train, y_train)
y_pred = nb.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
In [3]:
plt.show()
Decision Trees
Decision Trees are classification methods that are able to extract simple rules about the data features which are inferred from the input
dataset. Several algorithms for decision tree induction are available in the literature. Scikit-learn contains the implementation of the CART
(Classification and Regression Trees) induction algorithm.
Practical examples
Fist of all, we do all necessary imports.
In [1]:
import pandas as pd
import graphviz
Next, we load the Iris dataset, extract its values and labels and split them into train and test sets.
In [2]:
Then, we will fit and test a DecisionTreeClassifier. Scikit-learn does not implement any post-prunning step. So, to avoid overfitting, we can
control the tree size with the parameters min_samples_leaf, min_samples_split and max_depth.
In [3]:
# Creating a DecisionTreeClassifier.
# The criterion parameter indicates the measure used (possible values: 'gini' for the Gini index and
# 'entropy' for the information gain).
# The min_samples_leaf parameter indicates the minimum of objects required at a leaf node.
# The min_samples_split parameter indicates the minimum number of objects required to split an internal node.
# The max_depth parameter controls the maximum tree depth. Setting this parameter to None will grow the
# tree until all leaves are pure or until all leaves contain less than min_samples_split samples.
tree = DecisionTreeClassifier(criterion='gini',
min_samples_leaf=5,
min_samples_split=5,
max_depth=None,
random_state=seed)
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print('DecisionTreeClassifier accuracy score: {}'.format(accuracy))
Finally, we can plot the obtained tree to visualize the rules extracted from the dataset.
In [4]:
# Generating plot.
graph = graphviz.Source(graph_data)
graph.render(plot_title)
return graph
Out[4]:
Unfortunately, the DecisionTreeClassifier class does not handle categorical features directly. So, we might consider to transform them to
dummy variables. However, this approach must be taken with a grain of salt because decision trees tend to overfit on data
with a large number of features.
In [5]:
# We drop the 'stalk-root' feature because it is the only one containing missing values.
data = data.drop('stalk-root', axis=1)
# Creating a DecisionTreeClassifier.
tree = DecisionTreeClassifier(criterion='gini',
min_samples_leaf=5,
min_samples_split=5,
max_depth=None,
random_state=seed)
tree.fit(X_train, y_train)
Out[5]:
In [6]:
y_pred = tree.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print('DecisionTreeClassifier accuracy score: {}'.format(accuracy))
In [7]:
DecisionTreeClassifier max_depth: 6
In [8]:
# Creating a DecisionTreeClassifier.
tree = DecisionTreeClassifier(criterion='gini',
min_samples_leaf=5,
min_samples_split=5,
max_depth=3,
random_state=seed)
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print('DecisionTreeClassifier accuracy score: {}'.format(accuracy))
In [9]:
Out[9]:
n <= 0.5
gini = 0.4994
samples = 5361
value = [2777, 2584]
class = p
True False
Perceptron
The Perceptron is a very simple linear binary classifier. It basically maps and input vector x to a binary output f(x).
Given a weight vector w, the Perceptron's classfication rule is: f(x) = 1 if w ⋅ x + b > 0 or f(x) = 0 otherwise. Here, b is a bias value which is
responsible for shifting the Perceptron's hyperplane away from the origin.
Practical example
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
The most simple examples for Perceptron are the basic logic operations, such as: AND, OR and XOR.
x0 x1 y
0 0 0
1 0 0
0 1 0
1 1 1
In [2]:
Then, we plot the Perceptron's decision boundary. The colorbar to the left shows the scores achieved by w ⋅ x + b. Each point color indicates
a different class (blue = 1, red = 0).
In [3]:
cm = plt.cm.coolwarm_r
#cm = plt.cm.RdBu
thr = 0.0
xx, yy = np.meshgrid(np.arange(xmin - thr, xmax + thr, step), np.arange(ymin - thr, ymax + thr, step))
if hasattr(classifier, 'decision_function'):
Z = classifier.decision_function(np.hstack((xx.ravel()[:, np.newaxis], yy.ravel()[:, np.newaxis])))
else:
Z = classifier.predict_proba(np.hstack((xx.ravel()[:, np.newaxis], yy.ravel()[:, np.newaxis])))[:, 1]
Z = Z.reshape(xx.shape)
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
plt.xticks((0.0, 1.0))
plt.yticks((0.0, 1.0))
plt.title(title)
x0 x1 y
0 0 0
1 0 1
0 1 1
1 1 1
Below we run the Perceptron to the logical OR, print its achieved scores and plot its decision boundary.
In [4]:
x0 x1 y
0 0 0
1 0 1
0 1 1
1 1 0
In [5]:
Clearly, this is not a linear separable problem. In other words, it is not possible to separate the two classes with a single hyperplane.
This kind of problem motivates us to use Multilayer Perceptrons (MLPs), which are shown in the sequence.
A MLP is a neural network which is composed by at least three different layers: an input layer, a hidden layer and an output layer. Except for
the input layer, the remaining ones are composed by Perceptrons with nonlinear activation functions (e.g., sigmoid or tanh).
MLPs are usually trained using the backpropagation algorithm and are able to deal with not linearly separable problems.
In [6]:
# Creating a MLPClassifier.
# hidden_layer_sizes receive a tuple where each position i indicates the number of neurons
# in the ith hidden layer
# activation specifies the activation function (other options are: 'identity', 'logistic' and 'relu')
# max_iter indicates the maximum number of training iterations
# There are other parameters which can also be changed.
# See http://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html
mlp = MLPClassifier(hidden_layer_sizes=(5,),
activation='tanh',
max_iter=10000,
random_state=seed)
pred = mlp.predict_proba(X)
print("MLP's XOR probabilities:\n[class0, class1]\n{}".format(pred))
First of all, we load the dataset, encode its labels as int values and split it into training and test sets.
In [7]:
mlp = MLPClassifier(hidden_layer_sizes=(10,),
activation='tanh',
max_iter=10000,
random_state=seed)
mlp.fit(X_train, y_train)
y_pred = mlp.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("MLP's accuracy score: {}".format(accuracy))
Unfortunately, MLPs are very sensitive to different feature scales. So, it is normally necessary to normalize or rescale the input data.
In [9]:
# Creating a StandardScaler. This object normalizes features to zero mean and unit variance.
scaler = StandardScaler()
scaler.fit(X_train)
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Then, we generate a very simple linear separable dataset and plot it.
In [2]:
plot_data(X, y)
plt.show()
Next, we train a SVM classifier with linear kernel and plot the optimal hyperplane as well as the classifier margins.
In [3]:
plot_data(X, y)
plot_margins(svm, X, y)
plt.show()
In the above plot, the filled line represents the optimal hyperplane found while the dashed lines represent the hyperplanes defined by the
support vectors. The margin of the classifier is the distance between the optimal hyperplane and any of the support vector hyperplanes.
SVMs are linear classifiers. Since most of the real world problems are not linearly separable, how can we deal with them?
Next, we will show a very simple application of the Kernel Trick, which ables us to learn non-linear decision boundaries.
In [14]:
Then, we try to fit a custom SVM with linear kernel. Clearly, this classifier will not achieve good results.
In [5]:
plot_data(X, y)
plot_margins(svm, X, y)
plt.show()
However, if we apply a polynomial kernel of degree 2, we are able to learn the optimal decision boundary for this dataset.
In [6]:
svm = SVC(C=100, kernel='poly', degree=2, random_state=seed)
svm.fit(X, y)
plot_data(X, y)
plot_margins(svm, X, y)
plt.show()
The Kernel Trick consists of implicitly mapping a lower dimensional dataset, which is not linearly separable, to a higher dimensional space
where the data becomes linearly separable.
In the above example, the standard linear kernel calculates the standard dot product as the similarity between two vectors u and v. That is:
k(u, v) = u ⋅ v.
When we apply a polynomial kernel of degree 2, we are calculating the similarity between two vectors u and v as:
k(u, v) = (u ⋅ v) 2
k(u, v) = (u 1 v1 + u 2 v2 ) 2
k(u, v) = u 21 v21 + 2u 1 v1 u 2 v 2 + u 22 v 22
[ ][ ]
u 21 v 21
Finally, we will plot the original dataset in this new three dimensional space.
In [24]:
ax.set_xlabel('x[0] ** 2')
ax.set_ylabel('np.sqrt(2) * x[0] * x[1]')
ax.set_zlabel("x[1] ** 2")
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_zticklabels([])
plt.show()
As it can be seen, the original dataset is linear separable in this new three dimensional space.
Finally, as a last example, we will apply SVMs on the Breast Cancer dataset.
In [8]:
# Loading Breast Cancer dataset.
data = pd.read_csv('data/breast_cancer.csv')
Since this dataset has high dimensionality and probably is not linearly separable, we will apply a SVM with Radial Basis Function (RBF) kernel.
In [9]:
svm = SVC(kernel='rbf')
svm.fit(X_train, y_train)
y_pred = svm.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("SVM's accuracy score: {}".format(accuracy))
Unfortunately, SVMs are sensitive to data scale. Thus, we will standartize the dataset and train the SVM again.
In [10]:
scaler = StandardScaler()
scaler.fit(X_train)
Multiclass problems
SVMs were designed to deal with binary classification problems. Several approaches are available to deal with multiclass problems. Some of
them are:
One-vs-one classifiers: suppose the classification problem is composed by k classes. Thus, k(k − 1) /2 SVMs are fitted, each one for a
different pair of classes. For prediction, the class that received most of the votes is returned as output.
One-vs-all classifiers: suppose the classification problem is composed by k classes. Then, k different classifiers are fitted, one for
each class.
First we do all necessary imports, load the breast cancer dataset and define a method to plot a classifier's decision boundary.
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Random seed.
seed = 10
# Extracting the instances data. In this example we will consider only the first two features to be able to
# plot the data and the decision boundaries of the classifiers.
X = data.drop('Name', axis=1).values[:, :2]
colormap = plt.cm.Paired
plt.contourf(xx, yy, Z, cmap=colormap)
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
plt.title(title)
Voting classifier
The idea of the Voting Classifier is to combine different types of classifiers and to produce a prediction as the majority vote among them or
the argmax of the mean probability of a class. In scikit-learn, this approach is implemented in the VotingClassifier class.
In [2]:
plt.figure(figsize=(8, 8))
# Fitting a MLP.
mlp = MLPClassifier(hidden_layer_sizes=(10,), max_iter=10000, random_state=seed)
mlp.fit(X_train, y_train)
plt.subplot(2, 2, 2)
plot_decision_boundary(mlp, X_train, y_train, 'MLP decision boundary')
# Fitting a kNN.
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
plt.subplot(2, 2, 3)
plot_decision_boundary(knn, X_train, y_train, 'kNN decision boundary')
plt.tight_layout()
plt.show()
Bagging classifier
Bagging applies the same classifier on subsamples (usually with the same size) of the original dataset with replacement. In scikit-learn, this
method is implemented through BaggingClassifier class. Its predictions return the label with highest mean probability among the base
classifiers. If the base classifiers do not implement the predict_proba method, this class predicts the label by majority voting.
As mentioned in scikit-learn's documentation, the bagging method usually works well with more complex models (such as fully fitted
decision trees).
In [3]:
plt.figure(figsize=(8, 4))
tree = DecisionTreeClassifier(random_state=seed)
tree.fit(X_train, y_train)
plt.subplot(1, 2, 1)
plot_decision_boundary(tree, X_train, y_train, 'Decision Tree decision boundary')
plt.tight_layout()
plt.show()
Boosting classifier
The Boosting method tries to combine several weak classifiers (i.e., classifiers that are slightly better than random classifiers) into a strong
classifier. At each step, the procedure fits a new classifier with different weights on the objects from the training set. The idea is simple,
objects that are assigned the wrong label will have their weights increased in the next iteration, while the others will have their weights
decreased in the next iteration.
The most popular boosting algorithm of is AdaBoost. In scikit-learn, it is implemented in the AdaBoostClassifier class.
In [4]:
plt.figure(figsize=(8, 4))
boosting_clf = AdaBoostClassifier(n_estimators=50)
boosting_clf.fit(X_train, y_train)
plt.subplot(1, 2, 2)
plot_decision_boundary(boosting_clf, X_train, y_train, 'AdaBoost Classifier decision boundary')
plt.tight_layout()
plt.show()
Random Forest classifier
Random Forest consists of an ensemble method composed by multiple decision trees. Each tree is trained with a subsample with
replacement from the original dataset and, at each step, a node split is performed by choosing the best split among a random subset of the
features instead of the best split overall.
Many experimental machine learning studies suggest that Random Forest is one of the best classifiers from the literature. In scikit-learn, this
algorithm is implemented through RandomForestClassifier class.
In [5]:
plt.figure(figsize=(5, 5))
plot_decision_boundary(random_forest_clf, X_train, y_train, 'Random Forest Classifier decision boundary')
plt.show()