Leitura e análise exploratória


Neste lab vamos utilizar o iris dataset. Esse dataset contém 3 classes com 50 instâncias, onde cada classe representa um tipo de íris diferente. Tentaremos responder se é possível classificar as írias baseando-nos em suas características como altura e largura de suas pétalas e sépalas.

Importando os dados.

iris <- read.csv('data/iris-data.csv')


Podemos ver que os dados estão íntegros e prontos para serem explorados.

head(iris)
##    V1  V2  V3  V4          V5
## 1 5.1 3.5 1.4 0.2 Iris-setosa
## 2 4.9 3.0 1.4 0.2 Iris-setosa
## 3 4.7 3.2 1.3 0.2 Iris-setosa
## 4 4.6 3.1 1.5 0.2 Iris-setosa
## 5 5.0 3.6 1.4 0.2 Iris-setosa
## 6 5.4 3.9 1.7 0.4 Iris-setosa


Como podemos notar, as colunas estão com nomes poucos significativos, o que pode dificultar nossa análise e o entendimento da mesma. Portanto, vamos dar nomes ao bois.

names(iris) <- c("Sepala.Comprimento", "Sepala.Largura", "Petala.Comprimento", "Petala.Largura", "Especie")

head(iris)
##   Sepala.Comprimento Sepala.Largura Petala.Comprimento Petala.Largura
## 1                5.1            3.5                1.4            0.2
## 2                4.9            3.0                1.4            0.2
## 3                4.7            3.2                1.3            0.2
## 4                4.6            3.1                1.5            0.2
## 5                5.0            3.6                1.4            0.2
## 6                5.4            3.9                1.7            0.4
##       Especie
## 1 Iris-setosa
## 2 Iris-setosa
## 3 Iris-setosa
## 4 Iris-setosa
## 5 Iris-setosa
## 6 Iris-setosa


Assim, já podemos iniciar nossa análise exploratória. Este simples gráfico nos mostra a correlação entre o comprimento da pétala e sua largura.

plot(iris$Petala.Comprimento, 
     iris$Petala.Largura, 
     pch=21, bg=c("red","green3","blue")[unclass(iris$Especie)], 
     xlab="Comprimento da Pétala", 
     ylab="Largura da Pétala")


Aparentemente existe um correlação linear entre essas duas variáveis para cada espécie de íris. O que pode ser confirmado pela função cor.

cor(iris$Petala.Comprimento, iris$Petala.Largura)
## [1] 0.9627571


Neste próximo gráfico poderemos ver como as Larguras e Comprimentos das sépalas de cada espécie se distribuem.

sepalaPlot <- ggplot(aes(x = Sepala.Largura, y = Sepala.Comprimento), data=iris) + 
  theme_bw() +
  geom_smooth(aes(color = Especie), method = 'loess') + 
  labs(y = ("Largura da Sépala"), x = "Comprimento da Sépala")

sepalaPlot


Olhando o gráfico acima, fica fácil visualizar o quão distinguíveis as espécies são umas das outras.

Pré-processamento dos dados

Antes de construir o modelo, é necessário averiguar se os dados estão coerentes e normalizados. Como adquirimos o dataset do UCI Machine Learning Repository, podemos ter a confiaça de que os dados estão coerentes. Portanto veremos apenas se será necessário normalizá-los.

sepalaComp <- ggplot(data = iris, aes(x = "", y = Sepala.Comprimento)) + 
  theme_bw() + 
  geom_boxplot() + 
  labs(x = "", y = 'Comprimento da Sépala')

petalaComp <- ggplot(data = iris, aes(x = "", y = Petala.Comprimento)) + 
  theme_bw() + 
  geom_boxplot() + 
  labs(x = "", y = 'Comprimento da Pétala')

sepalaLarg <- ggplot(data = iris, aes(x = "", y = Sepala.Largura)) + 
  theme_bw() + 
  geom_boxplot() + 
  labs(x = "", y = 'Largura da Sépala')

petalaLarg <- ggplot(data = iris, aes(x = "", y = Petala.Largura)) + 
  theme_bw() + 
  geom_boxplot() + 
  labs(x = "", y = 'Largura da Pétala')

plot_grid(sepalaComp, petalaComp, sepalaLarg, petalaLarg, labels = "AUTO", hjust = 0, vjust = 1)


Como mostra o gráfico, os dados não variam muito entre si e portanto não precisam de normalização. Mas para fins de aprendizado, vamos fazê-lo.

iris[,5] <- as.numeric(iris[,5]) -1

# Transforma `iris` em uma matriz
iris <- as.matrix(iris)

# Remove dimnames
dimnames(iris) <- NULL

#Normaliza usando a função do keras
iris[,1:4] <- normalize(iris[,1:4])


Dados de teste e treino

Agora vamos dividir os dados em dados de teste e treino. Fazendo isso, asseguramos que podemores realizar avaliações honestas do desempenho de nosso modelo preditivo depois.

# Especificando o tamanho dos conjuntos de dados
ind <- sample(2, nrow(iris), replace=TRUE, prob=c(0.67, 0.33))

# Partindo o `iris` dataset
iris.training <- iris[ind==1, 1:4]
iris.test <- iris[ind==2, 1:4]

# Dividindo o atributo class
iris.trainingtarget <- iris[ind==1, 5]
iris.testtarget <- iris[ind==2, 5]


One Hot Enconding

Ainda falta um passo para que possamos começar a construção do modelo preditivo. Quando queremos construir um modelo de classificação multi-classe com redes neurais, geralmente é uma boa prática transformar a classe em uma matrix de booleanos que possui para cada classe um valor que indica a qual classe ela pertence. O keras facilita isso para nós.

# One hot encode classes do treino
iris.trainLabels <- to_categorical(iris.trainingtarget)

# One hot encode classes do teste
iris.testLabels <- to_categorical(iris.testtarget)

# Verificando o resultado das operações
head(iris.testLabels, 20)
##       [,1] [,2] [,3]
##  [1,]    1    0    0
##  [2,]    1    0    0
##  [3,]    1    0    0
##  [4,]    1    0    0
##  [5,]    1    0    0
##  [6,]    1    0    0
##  [7,]    1    0    0
##  [8,]    1    0    0
##  [9,]    1    0    0
## [10,]    1    0    0
## [11,]    1    0    0
## [12,]    1    0    0
## [13,]    1    0    0
## [14,]    1    0    0
## [15,]    0    1    0
## [16,]    0    1    0
## [17,]    0    1    0
## [18,]    0    1    0
## [19,]    0    1    0
## [20,]    0    1    0
Usando a função head podemos ver o resultado claramente o resultado do One Hot Encode. Para cada classe agora temos uma lista que possui valores que indicam a qual classe ela pertence.

Construção do modelo

Para começar a construção do modelo, primeiro devemos inicializar um modelo sequencial usando a função keras_model_sequential. Depois, estaremos aptos a construi o modelo.

model <- keras_model_sequential()


Entretanto, antes de começar, é uma boa ideia revisitar nosso questionamento inicial sobre o dataset: é possível predizer a espécie de uma certa flor íris? Sabemos que é mais fácil trabalhar com dados numéricos e após passarmos pela fase de OHC, nossos dados encontram-se em um estado ótimo para serem trabalhados por um MLP.

Construiremos uma simples rede neural com uma pilha de camadas densamente conectadas para resolver esse problema de classificação. Também usaremos as funções de ativação relu e softmax nas camadas ocultas e na de output, respectivamente.

model %>% 
    layer_dense(units = 8, activation = 'relu', input_shape = c(4)) %>% 
    layer_dense(units = 3, activation = 'softmax')


Compilando e treinando o modelo

Agora é a hora de compilarmos e treinarmos o modelo com os dados. Para compilar, configuramos o modelo para usar o otimizador adam a função de custo categorial_crossentropy. Finalmente, adiciomos o argumento accuracy para podermos medir os resultados do modelo.

model %>% compile(
     loss = 'categorical_crossentropy',
     optimizer = 'adam',
     metrics='accuracy'
 )


Agora treinaramos nosso modelo por 200 iterações em pedaços de tamanho 5.

modelFit <- model %>% fit(
     iris.training, 
     iris.trainLabels, 
     epochs = 200,
     batch_size = 5, 
     validation_split = 0.2
 )


Agora que o modelo está treinado, é hora de avaliá-lo e investigá-lo.

# Plota a perda do modelo nos dados de treino
plot(modelFit$metrics$loss, main="Perda do Modelo", xlab = "Iteração", ylab="Perda", col="blue", type="l")

# Plota a perda do modelo nos dados de teste
lines(modelFit$metrics$val_loss, col="green")

# Add legend
legend("topright", c("Treino","Teste"), col=c("blue", "green"), lty=c(1,1))
Podemos ver que o modelo teve um bom treinamento visto que a cada iteração sua perda foi diminuindo significativamente.

Predição de novos dados

Agora que nosso modelo foi devidamente criado e treinado, está na hora de realmente predizer as classes das íris que estão nos dados de teste. Usaremos a função predict para fazer isso. Depois disso, usaremos uma matriz de confusão para nos ajudar a avaliar os resultados.

# Avaliando nos dados de teste
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)

# Printando o score
print(score)
## $loss
## [1] 0.6957739
## 
## $acc
## [1] 0.6

Com uma acurácia de aproximadamente 63%, podemos ver que o modelo não está indo muito bem. Entraremos então agora na fase de tuning.

Ajustando o modelo

O fine-tuning de um modelo é provavelmente algo que será feito bastante ao longo da nossa jornada, pois poucos são os problemas de classificação/regressão que são resolvidos de forma tão direta. Existem decisões chave que devemos tomar na hora de construir/ajustar o modelo para que ele venha a ter um bom desempenho. Por exemplo: decidir o número de camadas e neurônios da nossa rede neural.

Adicionando camadas

Vamos ver o que acontece se simplesmente adicionarmos camadas.

model <- keras_model_sequential() 

# Add layers to model
model %>% 
    layer_dense(units = 8, activation = 'relu', input_shape = c(4)) %>% 
    layer_dense(units = 5, activation = 'relu') %>% 
    layer_dense(units = 3, activation = 'softmax')

# Compile the model
model %>% compile(
     loss = 'categorical_crossentropy',
     optimizer = 'adam',
     metrics = 'accuracy'
 )

# Fit the model to the data
model %>% fit(
     iris.training, iris.trainLabels, 
     epochs = 200, batch_size = 5, 
     validation_split = 0.2
 )

# Evaluate the model
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)

# Print the score
print(score)
## $loss
## [1] 0.3598852
## 
## $acc
## [1] 0.76

Como podemos ver, o score não aumentou muito, na verdade, manteve-se o mesmo. Isso pode ser explicado pela pequena quantidade de dados, o que leva à rede neural estagnar-se em seu aprendizado.

Considerações finais

Poderíamos aqui nos estender no tocante à otimização/ajuste do modelo, porém com a pequena quantidade de observações presentes no dataset, seria praticamente imperceptível as mudanças que ocorressem. Contudo, vemos como é fácil construir nossa rede neural usando keras para R e como este pacote nos oferece de maneira fácil, acesso aos métodos de otimização, métricas etc.

Referências

Este lab foi produzido basedo neste tutorial