Neste tutorial você vai aprender como criar uma Rede Neural de multi camadas ou Multi-Layer Perceptron (MLP).

Este tutorial é inspirado neste tutorial do Data Camp em inglês.

Deep Learning

O que é Deep Learning e Multi-Layer Perceptron?

Deep Learning é uma sub área de Aprendizado de máquina dentro do campo de Ciência da Computação. O aprendizado de máquina explora o estudo e construção de algoritmos que podem aprender de seus erros e fazer previsões sobre dados. Tais algoritmos são inspirados em como o cérebro funciona e é estruturado e geralmente são chamados de Redes Neurais Artificiais. Atualmente é uma das áreas mais em alta, e é utilizada para resolver problemas na área de robótica, reconhecimento de imagem e Inteligência Artificial.

A rede neural mais simples, de uma camada, é chamada de Perceptron e é inspirado nos neurônios. É constituídos de nós de entrada, os pesos de cada entrada, função de ativação e os nós de saída. Funciona basicamente da seguinte maneira: é feita uma soma ponderada dos dados de entrada (que sempre são numéricos) com seus pesos, o resultado dessa soma passa pela função de ativação que vai gerar o resultado na saída.

Observe que o perceptron só trabalha com dados numéricos, e você deve converter qualquer dado nominal em um formato numérico.

Uma rede de perceptrons é uma MLP e é o que vamos implementar em R neste tutorial. Multi-layer perceptrons é uma rede de perceptrons organizadas em camadas. As saídas de uma camada serve de entrada para a camada seguinte. Geralmente podemos dividir em camada de entrada (input layer), camada “escondida” (hidden layer, que é onde acontece a “mágica”) e a camada de saída (output layer). Frequentemente as MLP possuem os perceptrons completamente conectados, o que significa que todos estão ligados com todos.

Outra observaçao a se fazer, é que com uma Rede Neural de uma camada só, o perceptron, só é possível reprentar bem problemas lineares, e essa limitação é inexistente numa MLP que pode representar problemas complexos.

Deep Learning com Keras

O pacote do Keras em R é uma interface para o pacote Keras de Python. É uma api para redes neurais que utiliza TensorFlow, Microsoft Cognitive Toolkit (CNTK) or Theano. Basicamente o keras facilita a criação de uma rede neural, oferencendo suporte para que você se preocupe apenas com a construção do modelo.

Vamos começar a prática.

Intalando Keras

Execute os comandos abaixo para instalar o keras.

# Se não tiver o devtools no seu RStudio, é necessário instalá-lo.
install.packages("devtools")

# Instalar Keras
devtools::install_github("rstudio/keras")

# Carregar o Keras
library(keras)

# Instalar o TensorFlow e todo ambiente necessário para utilizar o Keras.
install_keras()
  • Se você não tiver o Anaconda para sua versão de Python, também será necessário instalá-lo para ter sucesso na instalação do TensorFlow. Outro problema que talvez você se depare, é que se seu RStudio não tiver atualizado, alguns pacotes necessários terão que ser atualizados, como por exemplo o ‘reticulate’. Como também será necessário ter o Rtools.

Carregando os dados.

Depois de ter instaldo o keras com sucesso, o próximo passo é carregar os dados. Existem 3 opções principais.
1. O Keras já vem com 3 datasets imbutidos. É só usar qualquer umas dessas três funções para carregar os dados.

# Carregar MNIST data
mnist <- dataset_mnist()

# Carregar CIFAR10 data
cifar10 <- dataset_cifar10()

# Carregar IMDB data
imdb <- dataset_imdb()
  1. Você pode criar seu próprio data set, com dados aleatórios. Por exemplo:
# Criar matriz de dados
data <- matrix(rexp(1000*784), nrow = 1000, ncol = 784)

# Criar valores desejados na saída.
labels <- matrix(round(runif(1000*10, min = 0, max = 9)), nrow = 1000, ncol = 10)  
  1. Carregar os dados de algum arquivo, por exemplo CSV. É essa opção que iremos utilizar para seguir no tutorial.
# Carregar arquivos iris
iris <- read.csv(url("http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"), header = FALSE) 

É utilizada a opcão de header = FALSE pois não precisaremos dos nomes das colunas para modelagem da MLP. A função read.csv cria um data frame, que é uma estrutura de dados do R`

Explorando os Dados

É sempre bom dar uma olhada nos dados, procurar entender o dominío do problema para que a manipulação do modelo seja mais eficiente. Para tal temos algumas funções do R que possibilitam analisar os dataframes. Já que entender o dominío do data set é importante, aqui vai algumas informações sobre os dados. Toda flor tem pétalas e sépalas, as pétalas geralmente são coloridas e as sépalas são mais parecidas com folhas. Na éspecie de lofr Iris, elas podem ser classificadas em 3 categorias(iris-setosa, iris-versicolor, iris-virginica) de acordo com as caracteristicas da sépala e da pétala.

Voltando a exploração dos dados, vamos lá.

# A função head retorna as primeiras linhas do dataframe.
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
#A função str mostra a estrutura dos dados.
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 ...

Temos um data frame com 150 objetos (linhas) e 5 variáveis (colunas). 4 variáveis são numéricas e 1 é um fator de 3 níveis, ou seja 3 classificações diferentes.

Como já foi dito acima, o data frame foi carregado sem nome para as colunas. Mas iremos adicioná-los para uma melhor compreensão nessa etapa de exploração. Ainda sobre explorar os dados, será que há alguma correlação entre o tamanho e a largura das Petalas? Vamos plotar o gráfico para observar melhor.

# Adicionar nomes para colunas
names(iris) <- c("Sepala.Tamanho", "Sepala.Largura", "Petala.Tamanho", "Petala.Largura", "Especies")

# Gráfico para visualizar a correlação entre o Tamanho e a Largura da pétala nas espécies.
plot(iris$Petala.Tamanho, 
     iris$Petala.Largura, 
     pch=21, bg=c("red","green3","blue")[unclass(iris$Especies)], 
     xlab="Petal Tamanho", 
     ylab="Petal Largura")

cor(iris$Petala.Tamanho, iris$Petala.Largura)
## [1] 0.9627571
M <- cor(iris[,1:4])
corrplot(M, method="circle")

Pré processando os Dados

Antes de construir o modelo, é necessário garantir algumas propriedades dos dados. Os dados devem estar limpos (sem presença de N/A), normalizados (se aplicável) e para que possamos validar nosso modelo, é necessário dividir os dados em TREINO e TESTE.

Uma função para observar algumas características dos dados é a summary().

summary(iris)
##  Sepala.Tamanho  Sepala.Largura  Petala.Tamanho  Petala.Largura 
##  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  
##             Especies 
##  Iris-setosa    :50  
##  Iris-versicolor:50  
##  Iris-virginica :50  
##                      
##                      
## 

É possível observar algumas informações estatíticas como max, min e média (entre outros), e nas variáveis que sáo do tipo Fator é possível observar a quantidade de cada fator. O summary tbm te mostra se tem N/A, caso aplicável. Analisando os dados, não parece necessário uma normalização, mas vamos normalizá-los a fim de aprender como faz.

Normalizando os dados

É possível criar a sua própria função de normalização e aplicar aos dados, seguindo os comandos abaixo.

# Crie sua prórpia função de nomralização
minhaNormalizacao <- function(x) {
  num <- x - min(x)
  denom <- max(x) - min(x)
  return (num/denom)
}

# Utilize a função para normalizar os dados
iris_norm <- as.data.frame(lapply(iris[1:4], minhaNormalizacao))

# Observe os valores novamente
summary(iris_norm)
##  Sepala.Tamanho   Sepala.Largura   Petala.Tamanho   Petala.Largura   
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.00000  
##  1st Qu.:0.2222   1st Qu.:0.3333   1st Qu.:0.1017   1st Qu.:0.08333  
##  Median :0.4167   Median :0.4167   Median :0.5678   Median :0.50000  
##  Mean   :0.4287   Mean   :0.4392   Mean   :0.4676   Mean   :0.45778  
##  3rd Qu.:0.5833   3rd Qu.:0.5417   3rd Qu.:0.6949   3rd Qu.:0.70833  
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.00000

Mas, já que estamos utilizando o keras, e este pacote possui uma função de normalização. Essa função só funciona com matrizes, então precisamos tranformar nossos dados em matrizes. E antes disso temos que transformar os dados nominais em numéricos. É só seguir os comandos abaixo.

# Modifica a coluna de Espécies para variáveis numéricas.
iris[,5] <- as.numeric(iris[,5]) -1

# Tranforma o data frame em matriz
iris <- as.matrix(iris)

# Seta os nomes das dimensões para NULL
dimnames(iris) <- NULL

# Normaliza os dados
iris[,1:4] <- normalize(iris[,1:4])

Separando os dados de treino e teste

Para fazer essa separação, iremos utilizar a função sample(). Basicamente iremos criar um vetor com 150 elementos (número de linhas dos dados), com os valores 1 e 2 aleatoriamente distribuidos com a probabilidade de 0.67 e 0.33. Ou seja nossos dados de treinos terão 67% dos dados originais, enquanto os dados de testes terão 33%.

# Determina a divisão dos "pedaços"
indice <- sample(2, nrow(iris), replace=TRUE, prob=c(0.67, 0.33))

# Divide os dados em teste e treino
iris.training <- iris[indice==1, 1:4]
iris.test <- iris[indice==2, 1:4]

# Separa os rótulos do modelo de teste e treino
iris.trainingtarget <- iris[indice==1, 5]
iris.testtarget <- iris[indice==2, 5]

Para que o gerador de números aleatórios de R sigam uma mesma sequência, para que a reprodutibilidade seja garantida, iremos utilizar a função set.seed() passando qualquer inteiro como parâmetro. Neste tutorial utilizamos o número 100 É utilizado o replace=TRUE para que depois de atribuir um número a linha, seja possível utilizar esse número novamente

One-hot Enconding

Só mais um passo antes de começar a construir o modelo, que é adequar os rótulos de saída para um formato que melhor funcione para a redes neurais. Para fazer isso, é necessário transformar o vetor de saída, que tem um valor para cada classe, para uma matriz de boolean que indica se a classe está presente ou não. Por exemplo, neste caso, saíremos de um vetor com valores [1,2,3] para [0,0,1], [0,1,0], [1,0,0]. O pacote do keras tem uma função que oferce essa tranformação.

# Modifica os tórulo do treino para one-hot enconding
iris.trainLabels <- to_categorical(iris.trainingtarget)

# Modifica os tórulo do teste para one-hot enconding
iris.testLabels <- to_categorical(iris.testtarget)

# Mostra o valor dos rótulos de teste
print(head(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

Construindo o Modelo

Para iniciar a construção do modelo, contaremos com a ajuda da função keras_model_sequential(). E então vamos adicionar as camadas da rede neural. Para função de ativação, utilizaremos a função relu, que é uma das mais comuns utilizadas e é ideal para quem está começando a aprender. Essa função de ativação é utilizada na “hidden layer”. Para camada de saída (output layer) utilizaremos a função softmax que mantém os valores de saída entre 0 e 1.

# Inicializa o modelo
model <- keras_model_sequential() 

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

A camda de saída tem 3 unidades, que é uma pra cada classe de classificação (versicolor, virginica or setosa). Na camada anterior, temos 8 nós escondidos e um input_shape de 4, que é um pra cada coluna de variável dos dados.

Para analisar o modelo, podemos usar as seguintes funções:

# Resumo do 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
## ___________________________________________________________________________
# 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}}]
# Configuração da camada
get_layer(model, index = 1)

# Lista das camadas
model$layers

# Lista das entradas
model$input

# Lista das saídas
model$outputs

Compilando e ajustando o modelo

Depois de configurar a arquiterura do modelo, vamos compilar e ajustá-lo para uma melhor perfomance nos dados. Para compilar, iremos utilizar a função compile(). Nessa função é necesário setar alguns parametros obrigatórios, o loss e o optimizer. Se desejar monitorar a acurácia durante o treino, é so setar o parametro metrics para accuracy.

# Compila o modelo
model %>% compile(
     loss = 'categorical_crossentropy',
     optimizer = 'adam',
     metrics = 'accuracy'
 )

Escolhemos a função categorical_crossentropy para função de perda, porque desejamos classificar os dados em 3 classes, se fosse em apenas duas classes, seria mais recomendado utilizar a função binary_crossentropy. Para a função de otimização temos várias opções, as mais populares são Stochastic Gradient Descent (SGD), ADAM e RMSprop, cada um tem alguns parametros específicos a ser tunado.

Para ajustar o modelo, iremos usar a função fit() com 200 iterações em 5 lotes. Definindo as iterações e os lotes ajuda a melhorar eficiência pois evitar de carregar todas as possíveis entrada de uma vez só. Armazenaremos numa variável para algumas visualizações futuras.

# Ajusta o modelo 
history <-model %>% fit(
     iris.training, 
     iris.trainLabels, 
     epochs = 200, 
     batch_size = 5, 
     validation_split = 0.2
 )

Visualizando o histórico de treino

É possível observar o comportamento do treino a partir dos seguintes gráficos:

# Gráfico do treinamento do modelo
plot(history)

O gráfico de cima é relacionado a função de perda (loss), onde a cor verde representa os dados de validação e a vermelha, os dados de treino. O mesmo acontece para o gráfico de baixo, só que representa a acurácia (acc).
É importante observar que:
* Se a acurácia do treino está crescendo, mas a acurácia da validação está decrescendo é um sinal de overfitting (quando o modelo memoriza os dados ao invés de aprendê-los)
* Se a acurácia do treino e da validação são sempre crescentes, siginifca que o modelo está sendo bem treinado.

Predição de novos valores e avaliação dos modelos

O “último” passo agora é verificar a predição da rede neural para novos valores.

# Predição das classes para os dados de teste
classes <- model %>% predict_classes(iris.test, batch_size = 128)

# Avaliando rótulos dos testes
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)

# Pontuação de acerto
print(score)
## $loss
## [1] 0.4489071
## 
## $acc
## [1] 0.6530612

Refinando o Modelo

Esse é realmente o “último” passo e um dos mais trabalhosos. Modificar os parametros do modelo para aumentar o índice da métrica utilizada, que no nosso caso foi a acurácia. Além de modificar os valores dos epochs e dos lotes batch, tem outros três modos de otimizar o modelo.
1. Adicionando camadas
2. Aumentando o número de nós “escondidos”
3. Mudando o parametro de otimização da função de compile.

Adicionando camadas

Vamos observar o que acontece se adicionarmos mais uma camadas?

# Inicializa o modelo
model <- keras_model_sequential() 

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

# Compila o modelo
model %>% compile(
     loss = 'categorical_crossentropy',
     optimizer = 'adam',
     metrics = 'accuracy'
 )

# Salva o histórico do treino
history <- model %>% fit(
  iris.training, iris.trainLabels, 
  epochs = 200, batch_size = 5,
  validation_split = 0.2
 )


# Avalia o modelo
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)

# Pontuação do modelo
print(score)
## $loss
## [1] 0.5102868
## 
## $acc
## [1] 0.6530612
# Gráfico do histórico
plot(history)

Adicionando nós escondidos

Vamos adicionar mais nós na camada escondida.

# Inicializa o modelo
model <- keras_model_sequential() 

# Adiciona camadas
model %>% 
    layer_dense(units = 28, activation = 'relu', input_shape = c(4)) %>% 
    layer_dense(units = 3, activation = 'softmax')

# Compila o modelo
model %>% compile(
     loss = 'categorical_crossentropy',
     optimizer = 'adam',
     metrics = 'accuracy'
 )

# Salva o histórico do treino
history <- model %>% fit(
  iris.training, iris.trainLabels, 
  epochs = 200, batch_size = 5,
  validation_split = 0.2
 )

# Avalia o modelo
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)

# Pontuação do modelo
print(score)
## $loss
## [1] 0.2907182
## 
## $acc
## [1] 0.877551
#Gráfico do histórico do modelo
plot(history)

Otimização de parametros

Se mudarmos a função de otimização, o que acontece? Utilizaremos a função de otimização por gradiente descentendente e ela requer um parametro extra, a taxa de aprendizagem (learning rate)

# Inicializa o modelo
model <- keras_model_sequential() 

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

# Inicializa uma funcão de otimização
sgd <- optimizer_sgd(lr = 0.01)

# Compila o modelo
model %>% compile(optimizer=sgd, 
                  loss='categorical_crossentropy', 
                  metrics='accuracy')

# Salva o histórico do treino
history <- model %>% fit(
  iris.training, iris.trainLabels, 
  epochs = 200, batch_size = 5, 
  validation_split = 0.2
 )

# Avalia o modelo
score <- model %>% evaluate(iris.test, iris.testLabels, batch_size = 128)

# Pontuação do modelo
print(score)
## $loss
## [1] 0.6216444
## 
## $acc
## [1] 0.6530612
#Gráfico do histórico do modelo
plot(history)

Salvando, carregando e exportando o modelo

O keras oferece algumas opções para salvar e exportar seu modelo. Utilizando as funções save_model_hdf5() e load_model_hdf5() é possível salvar e recuperar o modelo, respectivamente.
Também é possível salvar e recuperar os pesos através das funções save_model_weights_hdf5() e load_model_weights_hdf5().

Também é possível salvar o modelo em arquivos JSON ou YAML, através das funções model_to_json() e model_to_yaml() para salvar, e model_from_json() e model_from yaml().

Você conseguiu!

Parabéns, você completou este tutorial, mas é só o começo. Escolha um dataset diferente e aplique seus conhecimentos sobre ele. Só mais uma coisa, aqui está a documentação do keras.

Divirta-se!