Texto original: https://www.datacamp.com/community/tutorials/keras-r-deep-learning
O primeiro passo é instalar o keras no RStudio. Para isso precisamos instalar o pacote devtools e tensorflow, através dos seguintes comandos:
install.packages(“devtools”) e install.packages(“tensorflow”)
Instalar o tensorflow requer a instalação prévia do Python.
Em seguida, instalamos o keras através do console com o comando devtools::install_github(“rstudio/keras”).
Agora podemos importar as bibliotecas:
knitr::opts_chunk$set(echo = TRUE)
#install.packages("keras")
#install.packages("tensorflow")
library(keras)
use_condaenv("r-tensorflow")
library(tensorflow)
#install_keras()
#install_tensorflow()
Para carregar os dados, usamos a função read.csv(). Nesse tutorial foram utilizados os dados fornecidos pelo UCI Machine Learning Repository.
#mnist <- dataset_mnist()
iris <- read.csv(url("http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"), header = FALSE)
Usaremos as funções abaixo para checar se a leitura dos dados foi feita corretamente:
A função head() retorna a primeira parte do data frame
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
Já str() proporciona uma visualização compacta da estrutura interna
str(iris)
## 'data.frame': 150 obs. of 5 variables:
## $ V1: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ V2: num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ V3: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ V4: num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ V5: Factor w/ 3 levels "Iris-setosa",..: 1 1 1 1 1 1 1 1 1 1 ...
Por fim, com a função dim() obtemos as dimensões
dim(iris)
## [1] 150 5
O gráfico indica a correlação (dependência) possitiva entre comprimento (length) e largura (width) para diferentes espécies da flor de Íris.
names(iris) <- c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width", "Species")
plot(iris$Petal.Length,
iris$Petal.Width,
pch=21, bg=c("red","green3","blue")[unclass(iris$Species)],
xlab="Petal Length",
ylab="Petal Width")
cor(iris$Petal.Length, iris$Petal.Width)
## [1] 0.9627571
Podemos confirmar essa informação plotando a correlação entre os atributos com a função cor().
library(corrplot)
## corrplot 0.84 loaded
M <- cor(iris[,1:4])
corrplot(M, method="circle")
A etapa de pré-processamento de dados compreende a aplicação de técnicas para realizar a limpeza, normalização e divisão (em treino e teste) dos dados.
Como observamos anteriormente, ao inspecionar os dados, a função head() e str() não indicaram nada fora do esperado. Agora usaremos a função summary() para confirmar que os dados estão suficientemente limpos.
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
##
##
##
Os valores dos atributos variam de 0.1 à 7.9, o que é considerado aceitável. Sendo assim, não seria necessário normalizar os dados. Entretanto, queremos estudar o efeito da normalização nos dados. A normalização busca reduzir a redundância de dados. Para isso foi feita uma função de normalização min-max, que linearmente transforma os dados para a função (x-min)/(max-min). Além da função criada, usamos o lapply() para normalizar os dados.
normalize <- function(x) {
num <- x - min(x)
denom <- max(x) - min(x)
return (num/denom)
}
iris_norm <- as.data.frame(lapply(iris[1:4], normalize))
head(iris)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 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
Para que a função normalize() seja usada no keras, é necessário garantir que estamos trabalhando com uma matriz, onde os elementos da mesma devem ser do mesmo tipo. Como verificamos anteriormente através da função str(), os valores de Species são do tipo fator, enquanto o restante dos valores é numérico. Sendo assim, vamos converter os dados para o tipo numérico através da função as.numeric(). Já para fazer a conversão dos dados para uma matriz, usamos a função as.matrix().
iris[,5] <- as.numeric(iris[,5]) -1
iris <- as.matrix(iris)
dimnames(iris) <- NULL
iris[,1:4] <- normalize(iris[,1:4])
summary(iris)
## V1 V2 V3 V4
## Min. :0.5385 Min. :0.2436 Min. :0.1154 Min. :0.00000
## 1st Qu.:0.6410 1st Qu.:0.3462 1st Qu.:0.1923 1st Qu.:0.02564
## Median :0.7308 Median :0.3718 Median :0.5449 Median :0.15385
## Mean :0.7363 Mean :0.3787 Mean :0.4691 Mean :0.14085
## 3rd Qu.:0.8077 3rd Qu.:0.4103 3rd Qu.:0.6410 3rd Qu.:0.21795
## Max. :1.0000 Max. :0.5513 Max. :0.8718 Max. :0.30769
## V5
## Min. :0
## 1st Qu.:0
## Median :1
## Mean :1
## 3rd Qu.:2
## Max. :2
Para que possamos começar a construção do modelo, vamos dividir os dados originais em treino e teste, isso garante que as avaliações do desempenho de seu modelo de previsão sejam honestas.
Usamos a função sample () para tirar uma amostra com um tamanho que é definido como o número de linhas do conjunto de dados, nesse caso, 150. Escolhemos de um vetor de 2 elementos e atribuimos 1 ou 2 para as 150 linhas do conjunto de dados. A atribuição dos elementos está sujeita a pesos de probabilidade de 0,67 e 0,33.
# Determinando o tamanho da amostra
ind <- sample(2, nrow(iris), replace=TRUE, prob=c(0.67, 0.33))
# Dividindo os dados
iris.training <- iris[ind==1, 1:4]
iris.test <- iris[ind==2, 1:4]
# Dividindo o atributo de classe
iris.trainingtarget <- iris[ind==1, 5]
iris.testtarget <- iris[ind==2, 5]
Quando modelamos problemas de classificação multi-classe com redes neurais, costuma ser uma boa prática certificar-se de que transformamos seu atributo-alvo de um vetor que contém valores para cada valor de classe para uma matriz com um booleano para cada valor de classe, independentemente de uma determinada instância possuir ou não esse valor.
A função to_categorical() do keras faz esse tratamento, que é conhecido como One-Hot Encoding (OHE):
# Valores-alvo de treinamento de OHE
iris.trainLabels <- to_categorical(iris.trainingtarget)
# Valores-alvo de teste de OHE
iris.testLabels <- to_categorical(iris.testtarget)
# Imprimindo iris.testLabels para confirmar o 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,] 1 0 0
## [20,] 1 0 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 1 0
## [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
Para começar a construção do modelo, é necessário primeiramente inicializar um modelo sequencial através da função keras_model_sequential(). Estamos trabalhando com dados numéricos, pré-processados e utilizamos o OHE para obter os valores da variável-alvo, onde uma flor pode ser do tipo versicolor, setosa ou virginica, o que é refletido com os valores binários 1 e 0. Tendo em vista essas características, usaremos uma rede neural totalmente conectada, a multi-layer perceptron. Com isso, buscamos construir uma pilha simples de camadas totalmente conectadas. Usaremos também a função de ativação relu, como forma de familiarização com o keras, já que essa é uma das funções mais comuns. Além disso, usaremos também a função de ativação softmax, para garantir que os valores de saída estão no intervalo de 0 e 1, podendo ser usadas como probabilidades previstas.
# Inicializando um modelo seqüencial
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')
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
## ___________________________________________________________________________
Através da função summary() e das demais funções abaixo, podemos inspecionar melhor o modelo.
# Obtendo a configuração do modelo
get_config(model)
## [{'class_name': 'Dense', 'config': {'name': 'dense_1', 'trainable': True, 'batch_input_shape': (None, 4), 'dtype': 'float32', 'units': 8, 'activation': 'relu', 'use_bias': True, 'kernel_initializer': {'class_name': 'VarianceScaling', 'config': {'scale': 1.0, 'mode': 'fan_avg', 'distribution': 'uniform', 'seed': None}}, 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, 'kernel_regularizer': None, 'bias_regularizer': None, 'activity_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}}, {'class_name': 'Dense', 'config': {'name': 'dense_2', 'trainable': True, 'units': 3, 'activation': 'softmax', 'use_bias': True, 'kernel_initializer': {'class_name': 'VarianceScaling', 'config': {'scale': 1.0, 'mode': 'fan_avg', 'distribution': 'uniform', 'seed': None}}, 'bias_initializer': {'class_name': 'Zeros', 'config': {}}, 'kernel_regularizer': None, 'bias_regularizer': None, 'activity_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}}]
# Obtendo a configuração das camadas
get_layer(model, index = 1)
## <keras.layers.core.Dense>
# 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)
Para compilar o modelo, vamos configurá-lo com o otimizador adam e a função de perda categorical_crossentropy. Também vamos monitorar a acurácia durante o treino, através do argumento metrics.
model %>% compile(
loss = 'categorical_crossentropy',
optimizer = 'adam',
metrics = 'accuracy'
)
Agora vamos ajustar o modelo aos dados. Nesse caso, treinamos o modelo por 200 iterações sobre todas as amostras em iris.training e iris.trainLabels, em 5 lotes de amostras.
# Ajustando o modelo
model %>% fit(
iris.training,
iris.trainLabels,
epochs = 200,
batch_size = 5,
validation_split = 0.2
)
history <- model %>% fit(
iris.training,
iris.trainLabels,
epochs = 200,
batch_size = 5,
validation_split = 0.2
)
plot(history)
É importante saber que loss e acc indicam a perda e acurácia do modelo para os dados de treinamento, enquanto val_loss e val_acc são as mesmas métricas para os dados de teste e de validação.
# Plotando a perda do modelo para os dados de treino
plot(history$metrics$loss, main="Model Loss", xlab = "epoch", ylab="loss", col="blue", type="l")
# Plotando a perda do modelo para os dados de teste
lines(history$metrics$val_loss, col="green")
legend("topright", c("train","test"), col=c("blue", "green"), lty=c(1,1))
No gráfico anterior, plotamos a perda do modelo nos dados de treino e de teste. Agora faremos o mesmo para a acurácia:
# Plotando a acurácia para os dados de treino
plot(history$metrics$acc, main="Model Accuracy", xlab = "epoch", ylab="accuracy", col="blue", type="l")
# Plotando a acurácia para os dados de validação
lines(history$metrics$val_acc, col="green")
legend("bottomright", c("train","test"), col=c("blue", "green"), lty=c(1,1))
Agora vamos usar o modelo para prever os labels do conjunto de testes iris.test, através da função predict(). Depois, imprimimos a matriz de confusão para verificar as previsões e os labels reais dos dados de iris.test com a função table().
# Predizendo as classes para os dados de teste
classes <- model %>% predict_classes(iris.test, batch_size = 128)
# Matriz de confusão
table(iris.testtarget, classes)
## classes
## iris.testtarget 0 1 2
## 0 20 0 0
## 1 0 17 0
## 2 0 4 11
Usamos a função evaluate() para avaliar o modelo.
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)
#Imprime o score
print(score)
## $loss
## [1] 0.2285304
##
## $acc
## [1] 0.9230769
Podemos observar que o modelo teve uma perda acentuada. Vamos tentar melhorar o modelo adicionando camadas, aumentando o número de hidden units e arrumando os parâmetros de otimização para a função compile(). Ao imprimir o score, o resultado era dado corretamente, mas a seguinte mensagem de alerta era dada a cada uma das 200 iterações: “UserWarning: Method on_batch_end() is slow compared to the batch update (0.128583). Check your callbacks.”. Afim de facilitar a visualização, parte do código foi comentada e os resultados obtidos colocados abaixo do código comentado.
# Inicializando o modelo sequencial
#model <- keras_model_sequential()
# Adicionando 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'
# )
# Ajustando o modelo aos dados
#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)
#print(score)
$loss [1] 0.2607501
$acc [1] 0.8958333
# Initialize a sequential model
model <- keras_model_sequential()
# Add layers to the 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'
)
# Save the training history in history
history <- model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Plot the model loss
plot(history$metrics$loss, main="Model Loss", xlab = "epoch", ylab="loss", col="blue", type="l")
lines(history$metrics$val_loss, col="green")
legend("topright", c("train","test"), col=c("blue", "green"), lty=c(1,1))
# Plot the model accuracy
plot(history$metrics$acc, main="Model Accuracy", xlab = "epoch", ylab="accuracy", col="blue", type="l")
lines(history$metrics$val_acc, col="green")
legend("bottomright", c("train","test"), col=c("blue", "green"), lty=c(1,1))
Além de adicionar camadas e hidden units, podemos tentar ajustar os parâmetros de otimização.
# Inicializa um modelo sequencial
#model <- keras_model_sequential()
# Incrementa o modelo adicionando camadas
#model %>%
# layer_dense(units = 8, activation = 'relu', input_shape = c(4)) %>%
# layer_dense(units = 3, activation = 'softmax')
# Define um otimizador
#sgd <- optimizer_sgd(lr = 0.01)
# Usa o otimizador para compilar o modelo
#model %>% compile(optimizer=sgd,
# loss='categorical_crossentropy',
# metrics='accuracy')
# Ajusta o modelo aos dados de treino
#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)
# Imprimindo as métricas de perda e acurácia
#print(score)
$loss [1] 0.5610743
$acc [1] 0.6041667
# Define um otimizador
sgd <- optimizer_sgd(lr = 0.01)
# Compila o modelo
model %>% compile(optimizer=sgd,
loss='categorical_crossentropy',
metrics='accuracy')
# Ajusta o modelo aos dados de treino
history <- model %>% fit(
iris.training, iris.trainLabels,
epochs = 200, batch_size = 5,
validation_split = 0.2
)
# Plotando a perda do modelo
plot(history$metrics$loss, main="Model Loss", xlab = "epoch", ylab="loss", col="blue", type="l")
lines(history$metrics$val_loss, col="green")
legend("topright", c("train","test"), col=c("blue", "green"), lty=c(1,1))
# Plotando a acurácia do modelo
plot(history$metrics$acc, main="Model Accuracy", xlab = "epoch", ylab="accuracy", col="blue", type="l")
lines(history$metrics$val_acc, col="green")
legend("bottomright", c("train","test"), col=c("blue", "green"), lty=c(1,1))
Podemos salvar e carregar o modelo através dos seguintes comandos: save_model_hdf5(model, “my_model.h5”) model <- load_model_hdf5(“my_model.h5”)
Também é possível salvar e carregar os pesos do modelo: save_model_weights_hdf5(“my_model_weights.h5”) model %>% load_model_weights_hdf5(“my_model_weights.h5”)
Por fim, podemos exportar as configurações do modelo para JSON ou YAML e carregá-las de volta ao workspace:
json_string <- model_to_json(model)
model <- model_from_json(json_string)
yaml_string <- model_to_yaml(model)
model <- model_from_yaml(yaml_string)