Este arquivo tem como objetivo ensinar a implementar e interpretar uma rede do tipo multilayer perceptron (MLP) em R e Keras, um framework de deep learning. O tutorial original pode ser encontrado em: https://www.datacamp.com/community/tutorials/keras-r-deep-learning.
O primeiro passo é instalar o pacote Keras. Perceba que, para instalá-lo, precisamos instalar antes o pacote devtools:
install.packages("devtools")
devtools::install_github("rstudio/keras")
Assim que os pacotes necessários forem instalados, já se pode importar a biblioteca Keras e instalar o Keras e o tensorflow no projeto:
# Importando pacote Keras
library(keras)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
# Instalando Keras e TensorFlow
install_keras()
## Using existing virtualenv at ~/.virtualenvs/r-tensorflow
## Upgrading pip ...
## Upgrading wheel ...
## Upgrading setuptools ...
## Installing TensorFlow ...
##
## Installation complete.
O Keras possui alguns dataframe embutidos, que podem ser listados em https://github.com/keras-team/keras/tree/master/keras/dataframes, carregados e utilizados no projeto a partir do próprio pacote. Também é possÃvel baixar dataframes no site da UCI, que contém repositórios de dados para machine learning e podem ser encontrados em http://archive.ics.uci.edu/ml/index.php.
Os dados utilizados neste tutorial podem ser acessados em http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data e constituem o dataframe mais famoso do UCI: o Iris dataframe.
# Carregando os dados
iris <- read.csv(url("http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"), header = FALSE)
# Nomeando as colunas do dataframe
names(iris) <- c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width", "Species")
Iris é um gênero de flores e contém sépalas e pétalas. As sépalas são as partes semelhantes a folhas, normalmente de cor verde, que envolvem a flor, e as pétalas são normalmente coloridas. Nas flores do gênero Iris, as sépalas e pétalas são basatante parecidas, como se pode observar nas figuras abaixo:
Diferentes flores do gênero Iris
A seguir, tem-se um gráfico que mostra uma correlação positiva entre o tamanho e a largura das pétalas (variáveis Petal.Length e Petal.Width) para cada espécie de Iris.
#Exibindo o gráfico de correlação entre tamanho e largura das pétalas
plot(iris$Petal.Length,
iris$Petal.Width,
pch=21, bg=c("red","green3","blue")[unclass(iris$Species)],
xlab="Petal Length",
ylab="Petal Width")
#Exibindo correlação entre o tamanho e a largura da pétala
cor(iris$Petal.Length, iris$Petal.Width)
## [1] 0.9627571
Antes de construir o modelo, precisamos preprocessar os dados a fim de limpá-los, normalizá-los e dividÃ-los em dados de treino e dados de teste.
Percebe-se, utilizando as funções summary e str que os dados estão limpos, restando normalizar e particionar esses dados.
# Exibindo sumário do dataframe
summary(iris)
## Sepal.Length Sepal.Width Petal.Length Petal.Width
## Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100
## 1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300
## Median :5.800 Median :3.000 Median :4.350 Median :1.300
## Mean :5.843 Mean :3.054 Mean :3.759 Mean :1.199
## 3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
## Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
## Species
## Iris-setosa :50
## Iris-versicolor:50
## Iris-virginica :50
##
##
##
# Verificando a estrutura do dataframe
str(iris)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "Iris-setosa",..: 1 1 1 1 1 1 1 1 1 1 ...
Para normalização, utilizou-se a função do pacote do keras e, para isso, precisou-se antes transformar os dados em uma matriz.
# Tranformando a variável espécies em numérica
iris[,5] <- as.numeric(iris[,5]) -1
# Tranformando os dados em matriz
iris <- as.matrix(iris)
# Tirando dimnames
dimnames(iris) <- NULL
# Normalizando os dados
iris[,1:4] <- normalize(iris[,1:4])
# Sumarizando o resultado
summary(iris)
## V1 V2 V3 V4
## Min. :0.6539 Min. :0.2384 Min. :0.1678 Min. :0.01473
## 1st Qu.:0.7153 1st Qu.:0.3267 1st Qu.:0.2509 1st Qu.:0.04873
## Median :0.7549 Median :0.3544 Median :0.5364 Median :0.16415
## Mean :0.7516 Mean :0.4048 Mean :0.4550 Mean :0.14096
## 3rd Qu.:0.7884 3rd Qu.:0.5252 3rd Qu.:0.5800 3rd Qu.:0.19753
## Max. :0.8609 Max. :0.6071 Max. :0.6370 Max. :0.28042
## V5
## Min. :0
## 1st Qu.:0
## Median :1
## Mean :1
## 3rd Qu.:2
## Max. :2
Particionando os dados em treino e teste
Com os dados devidamente tratados, precisamos particioná-los em dados de treino e de teste para garantir que o modelo é bom preditor.
# Setando a seed
set.seed(132)
# Determinando o tamanho da partição
partition <- sample(2, nrow(iris), replace=TRUE, prob=c(0.67, 0.33))
# Particionando os dados
iris.training <- iris[partition==1, 1:4]
iris.test <- iris[partition==2, 1:4]
# Separando a classe de espécie (a que se deseja prever)
iris.trainingtarget <- iris[partition==1, 5]
iris.testtarget <- iris[partition==2, 5]
Quando a classe que se quer predizer não é binária, é uma boa prática transformar o atributo em um vetor que contém valores para cada valor da classe e tratar cada um desses vetores como uma classe binária de cada valor da classe, independentemente de uma determinada instância possuir ou não esse valor de classe (one hot encode). Neste caso, como são três espécies de Iris, cria-se um vetor para cada espécie, totalizando três vetores. Cada vetor será uma classe binária de cada espécie diferente, informando se a espécie observada é a que ele representa ou não. O pacote keras já faz esse tratamento:
# One hot encode nos valores de treino
iris.trainLabels <- to_categorical(iris.trainingtarget)
# One hot encode nos valores de teste
iris.testLabels <- to_categorical(iris.testtarget)
# Resultado
print(iris.testLabels)
## [,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,] 1 0 0
## [16,] 1 0 0
## [17,] 1 0 0
## [18,] 1 0 0
## [19,] 0 1 0
## [20,] 0 1 0
## [21,] 0 1 0
## [22,] 0 1 0
## [23,] 0 1 0
## [24,] 0 1 0
## [25,] 0 1 0
## [26,] 0 1 0
## [27,] 0 1 0
## [28,] 0 1 0
## [29,] 0 1 0
## [30,] 0 1 0
## [31,] 0 1 0
## [32,] 0 1 0
## [33,] 0 1 0
## [34,] 0 1 0
## [35,] 0 1 0
## [36,] 0 1 0
## [37,] 0 0 1
## [38,] 0 0 1
## [39,] 0 0 1
## [40,] 0 0 1
## [41,] 0 0 1
## [42,] 0 0 1
## [43,] 0 0 1
## [44,] 0 0 1
## [45,] 0 0 1
## [46,] 0 0 1
## [47,] 0 0 1
## [48,] 0 0 1
## [49,] 0 0 1
## [50,] 0 0 1
## [51,] 0 0 1
## [52,] 0 0 1
## [53,] 0 0 1
Para casos como este em que a classe a ser predita é não-binária, a rede neural mais indicada é a multi-layer perceptron (MLP), um tipo de rede que possui neurônios completamente conectados. A função de ativação a ser utilizada é a relu, ativada nas camadas ocultas da rede. Outra funcção de ativação também é utilizada: a softmax, usada na camada de saÃda para certificar-se de que os valores de saÃda estão no intervalo entre 0 e 1 e podem ser usados como probabilidades de predição.
# Inicializando um modelo sequencial
model <- keras_model_sequential()
# Adicionando camadas ao modelo
model %>%
layer_dense(units = 8, activation = 'relu', input_shape = c(4)) %>%
layer_dense(units = 3, activation = 'softmax')
# Sumarizando o modelo
summary(model)
## ___________________________________________________________________________
## Layer (type) Output Shape Param #
## ===========================================================================
## dense_1 (Dense) (None, 8) 40
## ___________________________________________________________________________
## dense_2 (Dense) (None, 3) 27
## ===========================================================================
## Total params: 67
## Trainable params: 67
## Non-trainable params: 0
## ___________________________________________________________________________
# Listando as camadas do modelo
model$layers
## [[1]]
## <keras.layers.core.Dense>
##
## [[2]]
## <keras.layers.core.Dense>
# Listando os input tensors
model$inputs
## [[1]]
## Tensor("dense_1_input:0", shape=(?, 4), dtype=float32)
# Listando os output tensors
model$outputs
## [[1]]
## Tensor("dense_2/Softmax:0", shape=(?, 3), dtype=float32)
Compilando e treinando o modelo
Para treinar o modelo com os dados de treino, utiliza-se a função compile:
# Compilando o modelo
model %>% compile(
loss = 'categorical_crossentropy',
optimizer = 'adam',
metrics = 'accuracy'
)
# Treinando o modelo
model %>% fit(
iris.training,
iris.trainLabels,
epochs = 200,
batch_size = 5,
validation_split = 0.2
)
Para visualizar o histórico de treinamento do modelo em relação a acurácia e perda nos dados de treino e de teste, armazena-se em uma variável e depois exibe-a em um gráfico:
# Armazenando o histórico do treinamento
history <- model %>% fit(
iris.training,
iris.trainLabels,
epochs = 200,
batch_size = 5,
validation_split = 0.2
)
# Exibindo o histórico
plot(history)
Para predizer as espécies das flores baseando-se no tamanho e na largura das pétalas, usa-se a função predict_classes do keras e depois compara os resultados entre a espécie que o modelo preveu e a espécie real das flores:
# Predizendo as espécies dos dados de teste
classes <- model %>% predict_classes(iris.test, batch_size = 128)
# Comparando os resultados
table(iris.testtarget, classes)
## classes
## iris.testtarget 0 1 2
## 0 18 0 0
## 1 0 18 0
## 2 0 5 12
Para avaliar o modelo, usa-se a função evaluate e depois exibe o score que o modelo recebeu.
# Avaliando o modelo
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)
# Exibindo o score
print(score)
## $loss
## [1] 0.280623
##
## $acc
## [1] 0.9056604
O modelo teve uma grande perda. Para contornar a situação, normalmente ajustamos o nosso modelo, modificando o número de camadas, o número de hidden units para cada camada e ajustando os parâmetros de otimização.
Adicionando camadas
Vejamos o que acontece ao adicionarmos uma nova camada ao modelo.
# Inicializando um modelo sequencial
model <- keras_model_sequential()
# Addicionando camadas ao modelo
model %>%
layer_dense(units = 8, activation = 'relu', input_shape = c(4)) %>%
layer_dense(units = 5, activation = 'relu') %>%
layer_dense(units = 3, activation = 'softmax')
# Compilando o modelo
model %>% compile(
loss = 'categorical_crossentropy',
optimizer = 'adam',
metrics = 'accuracy'
)
# Treinando o modelo
model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Avaliando
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)
# Exibindo o score
print(score)
## $loss
## [1] 1.162084
##
## $acc
## [1] 0.3396226
# Save the training history in history
history <- model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
plot(history)
Adicionando hidden units
Vejamos o que acontece ao adicionarmos mais hidden units ao modelo.
# Inicializando um modelo sequencial
model <- keras_model_sequential()
# Addicionando camadas ao modelo
model %>%
layer_dense(units = 28, activation = 'relu', input_shape = c(4)) %>%
layer_dense(units = 3, activation = 'softmax')
# Compilando o modelo
model %>% compile(
loss = 'categorical_crossentropy',
optimizer = 'adam',
metrics = 'accuracy'
)
# Treinando o modelo
model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Avaliando
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)
# Exibindo o score
print(score)
## $loss
## [1] 0.2869686
##
## $acc
## [1] 0.9056604
# Salvando o histórico de treino
history <- model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Mostrando histórico
plot(history)
Ajustando parâmetros de otimização
Vejamos o que acontece ao definirmos um novo parâmetro de otimização ao modelo.
# Inicializando um modelo sequencial
model <- keras_model_sequential()
# Adicionando camadas
model %>%
layer_dense(units = 8, activation = 'relu', input_shape = c(4)) %>%
layer_dense(units = 3, activation = 'softmax')
# Definindo um otimizador
sgd <- optimizer_sgd(lr = 0.01)
# Usando o otimizador para compilar o modelo
model %>% compile(optimizer=sgd,
loss='categorical_crossentropy',
metrics='accuracy')
# Treinando modelo
model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Avaliando o modelo
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)
# Exibindo score
print(score)
## $loss
## [1] 0.6521385
##
## $acc
## [1] 0.6792453
# Salvando o histórico de treino
history <- model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Mostrando histórico
plot(history)