Tutorial 01 - Hello World em Aprendizagem de Máquina

Para começar o nosso estudo de aprendizagem de máquina vamos começar com um exemplo simples de aprendizagem. O objetivo aqui é entender o que é Aprendizagem de Máquina e como podemos usá-la. Não serão apresentados detalhes dos métodos aplicados, eles serão explicados ao longo do curso.

O material do curso será baseado no curso Intro to Machine Learning da Udacity e também no conteúdo de alguns livros:

[1]: Inteligência Artificial. Uma Abordagem de Aprendizado de Máquina (FACELI et. al, 2011)

[2]: Machine Learning: An Algorithmic Perspective, Second Edition (MARSLAND et. al, 2014)

[3]: Redes Neurais Artificiais Para Engenharia e Ciências Aplicadas. Fundamentos Teóricos e Aspectos Práticos (da SILVA I., 2016)

[4]: An Introduction to Statistical Learning with Applications in R (JAMES, G. et al, 2015)

Em termos de linguagem de programação, usaremos o Python e as bibliotecas do ScikitLearn e do Tensorflow. Bibliotecas auxiliares como Pandas, NumPy, Scipy, MatPlotLib dentre outras também serão necessárias.

O material dessa primeira aula é baseado em dois vídeos:

Vamos Começar :)

O primeiro passo é entender o que é Aprendizagem de Máquina (em inglês, Machine Learning). Uma definição que consta em [2] é a seguinte:

Machine Learning, then, is about making computers modify or adapt their actions (whether theses actions are making predictions, or controlling a robot) so that these actions get more accurate, where accuracy is measured by how well the chosen actions reflect the correct ones.

Podemos enxergar a aprendizagem de máquina como sendo um campo da Inteligência Artificial que visa prover os computadores a capacidade de modificar e adaptar as sua ações de acordo com o problema e, ao longo do processo, melhorar o seu desempenho.

É nessa área que se encontra a base de sistemas que usamos no dia a dia como:

Dentre tantas outras aplicações que serão detalhadas ao longo do curso.

Todos esses sistemas são possíveis graças a um amplo estudo de uma série de algoritmos que compoõe a aprendizagem de máquina. Existem disversas formas de classficiar esse conjunto de algoritmos. Uma forma simples é dividi-los em 4 grupos. Citando [2], temos:

  • Aprendizado Supervisionado (Supervised Learning): A training set of examples with the correct responses (targets) is provided and, based on this training set, the algorithm generalises to respond correctly to all possible inputs. This also colled learning from exemplars.

  • Aprendizado Não-Supervisionado (Unsupervised Learning): Correct responses are not provided, but instead the algorithm tries to identify similarities between the inputs so that inputs that have something in common are categorised together. The statistical approach to unsupervised learning is known as density estimation.

  • Aprendizado por Reforço (Reinforcement Learning): This is somewhere between supervised and unsupervised learning. The algortithm gets told when the answer is wrong, but dows not get told how to correct it. It has to explore and try out different possibilities until it works out how to get the answer right. Reinforcement learning is sometime called learning with a critic because of this monitor that scores the answer, but does not suggest improvements.

  • Aprendizado Evolucionário (Evolutionary Learning): Biological evolution can be seen as a learning process: biological organisms adapt to improve their survival rates and chance of having offspring in their environment. We'll look at how we can model this in a computer, using an idea of fitness, which corresponds to a score for how good the current solution is.

Neste curso iremos explorar alguns dos principais algoritmos de cada um dos grupos.

Hello World

Para começar vamos tentar entender um pouco de como funciona o processo que será tratado nos algoritmos com uma tarefa simples de classificação. A classificação é uma das técnicas de aprendizado supervisionado e consiste em dado um conjunto de dados, você deve classificar cada instância deste conjunto em uma classe. Isso será tema do próximo tutorial e será melhor detalhado.

Para simplificar, imagine a seguinte tarefa: desejo construir um programa que classifica laranjas e maças. Para entender o problema, assista: https://www.youtube.com/watch?v=cKxRvEZd3Mw

É fácil perceber que não podemos simplesmente programar todas as variações de características que temos em relação à maças e laranjas. No entanto, podemos aprender padrões que caracterizam uma maça e uma laranja. Se uma nova fruta for passada ao programa, a presença ou não desses padrões permitirá classifica-la em maça, laranja ou outra fruta.

Vamos trabalhar com uma base de dados de exemplo que possui dados coletados com características de laranjas e maças. Para simplificar, vamos trabalhar com duas características: peso e textura. Em aprendizagem de máquina, as caracterísicas que compõe nosso conjunto de dados são chamadas de features.

Peso Textura Classe (label)
150g Irregular Laranja
170g Irregular Laranja
140g Suave Maçã
130g Suave Maçã

Cada linha da nossa base de dados é chamada de instância (examples). Cada exemplo é classificado de acordo com um label ou classe. Nesse caso, iremos trabalhar com duas classes que são os tipos de frutas.

Toda a nossa tabela são os dados de treinamento. Entenda esses dados como aqueles que o nosso programa irá usar para aprender. De forma geral e bem simplificada, quanto mais dados nós tivermos, melhor o nosso programa irá aprender.

Vamos simular esse problema no código.


In [11]:
# Vamos transformar as informações textuais em números: (0) irregular, (1) Suave.
# Os labels também serão transformados em números: (0) Maçã e (1) Laranja

features = [[140, 1], [130, 1], [150, 0], [170, 0]]
labels = [0, 0, 1, 1]

Vamos agora criar um modelo baseado nesse conjunto de dados. Vamos utilizar o algoritmo de árvore de decisão para fazer isso.


In [12]:
from sklearn import tree 
clf = tree.DecisionTreeClassifier()

clf consiste no classificador baseado na árvore de decisão. Precisamos treina-lo com o conjunto da base de dados de treinamento.


In [14]:
clf = clf.fit(features, labels)

Observer que o classificador recebe com parâmetro as features e os labels. Esse classificador é um tipo de classificador supervisionado, logo precisa conhecer o "gabarito" das instâncias que estão sendo passadas.

Uma vez que temos o modelo construído, podemos utiliza-lo para classificar uma instância desconhecida.


In [16]:
# Peso 160 e Textura Irregular. Observe que esse tipo de fruta não está presente na base de dados.
print(clf.predict([[160, 0]]))


[1]

Ele classificou essa fruta como sendo uma Laranja.

HelloWorld++

Vamos estender um pouco mais esse HelloWorld. Claro que o exemplo anterior foi só para passar a idéia de funcionamento de um sistema desse tipo. No entanto, o nosso programa não está aprendendo muita coisa já que a quantidade de exemplos passada para ele é muito pequena. Vamos trabalhar com um exemplo um pouco maior.

Para esse exemplo, vamos utilizar o Iris Dataset. Esse é um clássico dataset utilizado na aprendizagem de máquina. Ele tem o propósito mais didático e a tarefa é classificar 3 espécies de um tipo de flor (Iris). A classificação é feita a partir de 4 características da planta: sepal length, sepal width, petal length e petal width.

As flores são classificadas em 3 tipos: Iris Setosa, Iris Versicolor e Iris Virginica.

Vamos para o código ;)

O primeiro passo é carregar a base de dados. Os arquivos desta base estão disponíveis no UCI Machine Learning Repository. No entanto, como é uma base bastante utilizada, o ScikitLearn permite importá-la diretamente da biblioteca.


In [54]:
from sklearn.datasets import load_iris

dataset_iris = load_iris()

Imprimindo as características:


In [55]:
print(dataset_iris.feature_names)


['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

Imprimindo os labels:


In [56]:
print(dataset_iris.target_names)


['setosa' 'versicolor' 'virginica']

Imprimindo os dados:


In [57]:
print(dataset_iris.data)


[[ 5.1  3.5  1.4  0.2]
 [ 4.9  3.   1.4  0.2]
 [ 4.7  3.2  1.3  0.2]
 [ 4.6  3.1  1.5  0.2]
 [ 5.   3.6  1.4  0.2]
 [ 5.4  3.9  1.7  0.4]
 [ 4.6  3.4  1.4  0.3]
 [ 5.   3.4  1.5  0.2]
 [ 4.4  2.9  1.4  0.2]
 [ 4.9  3.1  1.5  0.1]
 [ 5.4  3.7  1.5  0.2]
 [ 4.8  3.4  1.6  0.2]
 [ 4.8  3.   1.4  0.1]
 [ 4.3  3.   1.1  0.1]
 [ 5.8  4.   1.2  0.2]
 [ 5.7  4.4  1.5  0.4]
 [ 5.4  3.9  1.3  0.4]
 [ 5.1  3.5  1.4  0.3]
 [ 5.7  3.8  1.7  0.3]
 [ 5.1  3.8  1.5  0.3]
 [ 5.4  3.4  1.7  0.2]
 [ 5.1  3.7  1.5  0.4]
 [ 4.6  3.6  1.   0.2]
 [ 5.1  3.3  1.7  0.5]
 [ 4.8  3.4  1.9  0.2]
 [ 5.   3.   1.6  0.2]
 [ 5.   3.4  1.6  0.4]
 [ 5.2  3.5  1.5  0.2]
 [ 5.2  3.4  1.4  0.2]
 [ 4.7  3.2  1.6  0.2]
 [ 4.8  3.1  1.6  0.2]
 [ 5.4  3.4  1.5  0.4]
 [ 5.2  4.1  1.5  0.1]
 [ 5.5  4.2  1.4  0.2]
 [ 4.9  3.1  1.5  0.1]
 [ 5.   3.2  1.2  0.2]
 [ 5.5  3.5  1.3  0.2]
 [ 4.9  3.1  1.5  0.1]
 [ 4.4  3.   1.3  0.2]
 [ 5.1  3.4  1.5  0.2]
 [ 5.   3.5  1.3  0.3]
 [ 4.5  2.3  1.3  0.3]
 [ 4.4  3.2  1.3  0.2]
 [ 5.   3.5  1.6  0.6]
 [ 5.1  3.8  1.9  0.4]
 [ 4.8  3.   1.4  0.3]
 [ 5.1  3.8  1.6  0.2]
 [ 4.6  3.2  1.4  0.2]
 [ 5.3  3.7  1.5  0.2]
 [ 5.   3.3  1.4  0.2]
 [ 7.   3.2  4.7  1.4]
 [ 6.4  3.2  4.5  1.5]
 [ 6.9  3.1  4.9  1.5]
 [ 5.5  2.3  4.   1.3]
 [ 6.5  2.8  4.6  1.5]
 [ 5.7  2.8  4.5  1.3]
 [ 6.3  3.3  4.7  1.6]
 [ 4.9  2.4  3.3  1. ]
 [ 6.6  2.9  4.6  1.3]
 [ 5.2  2.7  3.9  1.4]
 [ 5.   2.   3.5  1. ]
 [ 5.9  3.   4.2  1.5]
 [ 6.   2.2  4.   1. ]
 [ 6.1  2.9  4.7  1.4]
 [ 5.6  2.9  3.6  1.3]
 [ 6.7  3.1  4.4  1.4]
 [ 5.6  3.   4.5  1.5]
 [ 5.8  2.7  4.1  1. ]
 [ 6.2  2.2  4.5  1.5]
 [ 5.6  2.5  3.9  1.1]
 [ 5.9  3.2  4.8  1.8]
 [ 6.1  2.8  4.   1.3]
 [ 6.3  2.5  4.9  1.5]
 [ 6.1  2.8  4.7  1.2]
 [ 6.4  2.9  4.3  1.3]
 [ 6.6  3.   4.4  1.4]
 [ 6.8  2.8  4.8  1.4]
 [ 6.7  3.   5.   1.7]
 [ 6.   2.9  4.5  1.5]
 [ 5.7  2.6  3.5  1. ]
 [ 5.5  2.4  3.8  1.1]
 [ 5.5  2.4  3.7  1. ]
 [ 5.8  2.7  3.9  1.2]
 [ 6.   2.7  5.1  1.6]
 [ 5.4  3.   4.5  1.5]
 [ 6.   3.4  4.5  1.6]
 [ 6.7  3.1  4.7  1.5]
 [ 6.3  2.3  4.4  1.3]
 [ 5.6  3.   4.1  1.3]
 [ 5.5  2.5  4.   1.3]
 [ 5.5  2.6  4.4  1.2]
 [ 6.1  3.   4.6  1.4]
 [ 5.8  2.6  4.   1.2]
 [ 5.   2.3  3.3  1. ]
 [ 5.6  2.7  4.2  1.3]
 [ 5.7  3.   4.2  1.2]
 [ 5.7  2.9  4.2  1.3]
 [ 6.2  2.9  4.3  1.3]
 [ 5.1  2.5  3.   1.1]
 [ 5.7  2.8  4.1  1.3]
 [ 6.3  3.3  6.   2.5]
 [ 5.8  2.7  5.1  1.9]
 [ 7.1  3.   5.9  2.1]
 [ 6.3  2.9  5.6  1.8]
 [ 6.5  3.   5.8  2.2]
 [ 7.6  3.   6.6  2.1]
 [ 4.9  2.5  4.5  1.7]
 [ 7.3  2.9  6.3  1.8]
 [ 6.7  2.5  5.8  1.8]
 [ 7.2  3.6  6.1  2.5]
 [ 6.5  3.2  5.1  2. ]
 [ 6.4  2.7  5.3  1.9]
 [ 6.8  3.   5.5  2.1]
 [ 5.7  2.5  5.   2. ]
 [ 5.8  2.8  5.1  2.4]
 [ 6.4  3.2  5.3  2.3]
 [ 6.5  3.   5.5  1.8]
 [ 7.7  3.8  6.7  2.2]
 [ 7.7  2.6  6.9  2.3]
 [ 6.   2.2  5.   1.5]
 [ 6.9  3.2  5.7  2.3]
 [ 5.6  2.8  4.9  2. ]
 [ 7.7  2.8  6.7  2. ]
 [ 6.3  2.7  4.9  1.8]
 [ 6.7  3.3  5.7  2.1]
 [ 7.2  3.2  6.   1.8]
 [ 6.2  2.8  4.8  1.8]
 [ 6.1  3.   4.9  1.8]
 [ 6.4  2.8  5.6  2.1]
 [ 7.2  3.   5.8  1.6]
 [ 7.4  2.8  6.1  1.9]
 [ 7.9  3.8  6.4  2. ]
 [ 6.4  2.8  5.6  2.2]
 [ 6.3  2.8  5.1  1.5]
 [ 6.1  2.6  5.6  1.4]
 [ 7.7  3.   6.1  2.3]
 [ 6.3  3.4  5.6  2.4]
 [ 6.4  3.1  5.5  1.8]
 [ 6.   3.   4.8  1.8]
 [ 6.9  3.1  5.4  2.1]
 [ 6.7  3.1  5.6  2.4]
 [ 6.9  3.1  5.1  2.3]
 [ 5.8  2.7  5.1  1.9]
 [ 6.8  3.2  5.9  2.3]
 [ 6.7  3.3  5.7  2.5]
 [ 6.7  3.   5.2  2.3]
 [ 6.3  2.5  5.   1.9]
 [ 6.5  3.   5.2  2. ]
 [ 6.2  3.4  5.4  2.3]
 [ 5.9  3.   5.1  1.8]]

In [58]:
# Nessa lista, 0 = setosa, 1 = versicolor e 2 = verginica
print(dataset_iris.target)


[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 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]

Antes de continuarmos, vale a pena mostrar que o Scikit-Learn exige alguns requisitos para se trabalhar com os dados. Esse tutorial não tem como objetivo fazer um estudo detalhado da biblioteca, mas é importante tomar conhecimento de tais requisitos para entender alguns exemplos que serão mostrados mais à frente. São eles:

  • As features e os labels devem ser armazenados em objetos distintos
  • Ambos devem ser numéricos
  • Ambos devem ser representados por uma Array Numpy
  • Ambos devem ter tamanhos específicos

Vamos ver estas informações no Iris-dataset.


In [59]:
# Verifique os tipos das features e das classes
print(type(dataset_iris.data))
print(type(dataset_iris.target))


<class 'numpy.ndarray'>
<class 'numpy.ndarray'>

In [60]:
# Verifique o tamanho das features (primeira dimensão = numero de instâncias, segunda dimensão = número de atributos)
print(dataset_iris.data.shape)


(150, 4)

In [61]:
# Verifique o tamanho dos labels
print(dataset_iris.target.shape)


(150,)

Quando importamos a base diretamente do ScikitLearn, as features e labels já vieram em objetos distintos. Só por questão de simplificação dos nomes, vou renomeá-los.


In [29]:
X = dataset_iris.data
Y = dataset_iris.target

Construindo e testando um modelo de treinamento

Uma vez que já temos nossa base de dados, o próximo passo é construir nosso modelo de aprendizagem de máquina capaz de utilizar o dataset. No entanto, antes de construirmos nosso modelo é preciso saber qual modelo desenvolver e para isso precisamos definir qual o nosso propósito na tarefa de treinamento.

Existem vários tipos de tarefas dentro da aprendizagem de máquina. Como dito anteriormente, vamos trabalhar com a tarefa de classificação. A classificação consiste em criar um modelo a partir de dados que estejam de alguma forma classificados. O modelo gerado é capaz de determinar qual classe uma instância pertence a partir dos dados que foram dados como entrada.

Na apresentação do dataset da Iris vimos que cada instância é classificada com um tipo (no caso, o tipo da espécie a qual a planta pertence). Sendo assim, vamos tratar esse problema como um problema de classificação. Existem outras tarefas dentro da aprendizagem de máquina, como: clusterização, agrupamento, dentre outras. Mais detalhes de cada uma deles serão apresentados na aula de aprendizagem de máquina.

O passo seguinte é construir o modelo. Para tal, vamos seguir 4 passos:

  • Passo 1: Importar o classificador que deseja utilizar
  • Passo 2: Instanciar o modelo
  • Passo 3: Treinar o modelo
  • Passo 4: Fazer predições para novos valores

Nessa apresentação, vamos continuar utilizando o modelo de Árvore de Decisão. O fato de usá-la nesta etapa é que é fácil visualizar o que o modelo está fazendo com os dados.

Para nosso exemplo, vamos treinar o modelo com um conjunto de dados e, em seguida, vamos testá-lo com um conjunto de dados que não foram utilizados para treinar. Para isso, vamos retirar algumas instâncias da base de treinamento e usá-las posteriormente para testá-la. Vamos chamar isso de dividir a base em base de treino e base de teste. É fácil perceber que não faz sentido testarmos nosso modelo com um padrão que ele já conhece. Por isso, faz-se necessária essa separação.


In [35]:
import numpy as np

# Determinando os índices que serão retirados da base de treino para formar a base de teste

test_idx = [0, 50, 100] # as instâncias 0, 50 e 100 da base de dados

# Criando a base de treino 

train_target = np.delete(dataset_iris.target, test_idx)
train_data = np.delete(dataset_iris.data, test_idx, axis=0)

# Criando a base de teste
test_target = dataset_iris.target[test_idx]
test_data = dataset_iris.data[test_idx]

print("Tamanho dos dados originais: ", dataset_iris.data.shape) #np.delete não modifica os dados originais
print("Tamanho do treinamento: ", train_data.shape) 
print("Tamanho do teste: ", test_data.shape)


Tamanho dos dados originais:  (150, 4)
Tamanho do treinamento:  (147, 4)
Tamanho do teste:  (3, 4)

Agora que já temos nosso dataset separado, vamos criar o classificador e treina-lo com os dados de treinamento.


In [37]:
clf = tree.DecisionTreeClassifier()
clf.fit(train_data, train_target)


Out[37]:
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_split=1e-07, min_samples_leaf=1,
            min_samples_split=2, min_weight_fraction_leaf=0.0,
            presort=False, random_state=None, splitter='best')

O classificador foi treinado, agora vamos utiliza-lo para classificar as instâncias da base de teste.


In [39]:
print(clf.predict(test_data))


[0 1 2]

Como estamos trabalhando com o aprendizado supervisionado, podemos comparar com o target que já conhecemos da base de teste.


In [41]:
print(test_target)


[0 1 2]

Observe que, neste caso, nosso classificador teve uma acurácia de 100% acertando todas as instâncias informadas. Claro que esse é só um exemplo e normalmente trabalhamos com valores de acurácias menores que 100%. No entanto, vale ressaltar que para algumas tarefas, como reconhecimento de imagens, as taxas de acurácias estão bem próximas de 100%.

Visualizando nosso modelo

A vantagem em se trablhar com a árvore de decisão é que podemos visualizar exatamente o que modelo faz. De forma geral, uma árvore de decisão é uma árvore que permite serparar o conjunto de dados. Cada nó da árvore é "uma pergunta" que direciona aquela instância ao longo da árvore. Nos nós folha da árvore se encontram as classes. Esse tipo de modelo será mais detalhado mais a frente no nosso curso.

Para isso, vamos utilizar um código que visualiza a árvore gerada.


In [49]:
from IPython.display import Image  
import pydotplus

dot_data = tree.export_graphviz(clf, out_file=None, 
                         feature_names=dataset_iris.feature_names,  
                         class_names=dataset_iris.target_names,  
                         filled=True, rounded=True,  
                         special_characters=True)  
graph = pydotplus.graph_from_dot_data(dot_data)  
Image(graph.create_png(), width=800)


Out[49]:

Observe que nos nós internos pergunta sim ou não para alguma característica. Por exemplo, no nó raiz a pergunta é "pedal width é menor ou igual a 0.8". Isso significa que se a instância que estou querendo classificar possui pedal width menor que 0.8 ela será classificada como setosa. Se isso não for true ela será redirecionada para outro nó que irá analisar outra característica. Esse processo continua até que consiga atingir um nó folha. Como execício faça a classificação, acompahando na tabela, para as instâncias de testes.


In [53]:
print(test_data)
print(test_target)


[[ 5.1  3.5  1.4  0.2]
 [ 7.   3.2  4.7  1.4]
 [ 6.3  3.3  6.   2.5]]
[0 1 2]

Vale ressaltar que essa árvore, que é nosso modelo, foi construído a partir do conjunto de dados que foi passado na etapa de treinamento. Se mudamos nosso conjunto de dados, uma nova representação do módelo, ou seja, uma nova árvore será criada.

Com isso chegamos ao final do nosso HelloWorld em aprendizagem de máquina. A partir dos próximos tutoriais vamos começar a detalhar os diversos algoritmos para as mais distintas tarefas de aprendizagem. Para começar, iremos trabalhar com os modelos supervisionados.

Até o próximo tutorial ;)