Este arquivo contém um breve tutorial de como utilizar RNN para treinar um modelo para predizer o número presente em uma imagem. Para treinar o modelo será utilizada a base de dados digit.recognizer que pode ser encontrada no site de disputas em DS Kaggle atráves do link https://www.kaggle.com/c/digit-recognizer/leaderboard.
Carregando pacotes e criando as matrizes de dados
Inicialmente vamos carregar os pacotes tidyverse e keras. Fixemos também as dimensões da matriz de dados tal como o número de classes da variável resposta.
suppressMessages(suppressWarnings(library(tidyverse)))
library(keras)
# Constants
img_rows <- 28
img_cols <- 28
n_classes <- 10
test <- read_rds("test.rds")
label <- read_rds("label_test.rds")
train <- read_rds("train.rds")Para visualizar uma imagem podemos selecionar uma linha do data.frame, e fazer algumas transformações na matriz que irá gerar cada imagem para que o número fique “em pé”. O resultado é visto abaixo:
set.seed(1)
# Visualizing a random digit
train %>%
select(-label, -Flag) %>%
sample_n(1) %>%
unlist() %>%
matrix(nrow = 28, byrow = TRUE) %>%
apply(2, rev) %>%
t() %>%
image() Podemos também ver vários digitos ao mesmo tempo e em escala de cinza. A função a seguir nos dá esta possibilidade:
# Start Function
Label <- function(digits, base, nrows = 28, ncols = 28){
stopifnot(digits %in% 1:nrow(base))
if ("label" %in% colnames(base))
{
cat("\nRemoving label column...")
base <- base %>% select(-label)
}
if ("Flag" %in% colnames(base))
{
cat("\nRemoving Flag column...")
base <- base %>% select(-Flag)
}
# Check graphical parameters
val <- par(no.readonly=TRUE)
n <- ceiling(sqrt(length(digits)))
par(mfrow = c(ceiling(length(digits)/n),n), mar = c(0.1, 0.1, 0.1, 0.1))
for (i in digits){
m <- base %>%
filter(row_number() == i) %>%
unlist() %>%
matrix(nrow = nrows, ncol = ncols, byrow = TRUE) %>%
apply(2, rev) %>%
t() %>%
image(col = grey.colors(255), axes = FALSE)
}
# reset the original graphics parameters
par(val)
}
# Call Function
Label(101:120, train)##
## Removing label column...
## Removing Flag column...
Agora, vamos criar as matrizes de treino e teste. Isto é necessário pois nos modelos de redes neurais precisamos explicitar as matrizes utilizadas no cálculo de cada modelo. É basicamente a mesma coisa de uma regressão, porém, no R para estimar modelos de regressão existem funções internas que nos ajudam nesta parte.
set.seed(123)
x_train <- train %>%
filter(Flag == 0) %>%
select(-label, -Flag) %>%
mutate_all(function(x) x/255) %>%
as.matrix()
x_test <- train %>%
filter(Flag == 1) %>%
select(-label, -Flag) %>%
mutate_all(function(x) x/255) %>%
as.matrix()
y_train <- train %>%
filter(Flag == 0) %>%
select(label) %>% as.matrix() %>%
keras::to_categorical(num_classes = 10)
y_test <- train %>%
filter(Flag == 1) %>%
select(label) %>%
as.matrix() %>%
to_categorical(num_classes = 10)Treinando modelo
Antes de iniciar o processo de estimação, verifique se o tensorflowestá instalado em sua máquina. Ele será utilizado pelo keras. Para instalar o tensorflow utilize: tensorflow::install_tensorflow().
Agora vamos começar criar o objeto para treinamento do modelo. Primeiro defina o método de modelagem a ser utilizado. O keras foi construído de maneira bem intuítiva e se encaixa com a metodologia tidy. Portanto, podemos utilizar o operador %>%.
Agora, definir os hiperparâmetros do modelo.
rnn_model %>%
layer_dense(units = 256, activation = 'relu', input_shape = c(img_cols*img_rows)) %>%
layer_dropout(rate = 0.4) %>%
layer_dense(units = 128, activation = 'relu') %>%
layer_dropout(rate = 0.2) %>%
layer_dense(units = n_classes, activation = 'softmax')Os modelos de RN são estimados por meio de camadas. O comando layer_dense define estas camadas. A seguir a descrição dizendo para que cada parâmetro serve:
units: Serve para informarmos o número de parâmetros.activation: Define qual será a função de ativação. Em redes neurais a função de ativação é o meio que utiliza-se para estimar os parâmetros de cada camada. Relacionando com regressão, a função de ativação equivale ao método de estimação dos parâmetros, por exemplo, quadrados mínimos.input_shape: Informa o número das colunas da matriz X que serão utilizadas.layer_drop: Este argumento faz com que sejam abandonadas algumas linhas da matriz de treinamento. É útil para não gerar overfitting.
Note o número de parâmetros é 10 e a função de ativação é a softmax. Este números de parâmetros da última camada representa o número de colunas a serem estimadas. É muito parecido com regressão multivariada. Ainda, note que as semelhanças não são atoa, já que o que estamos fazendo é estimar um modelo em que a variável resposta segue uma distribuição multinomial.
Quanto à função de ativação softmax, está serve para gerar estimativas no intervalo (0,1), tal como a função logística. A função softmax é dada por:
\[ \sigma(z)_j = \frac{e^{z_j}}{\sum^K_{k = 1} e^{z_j}} \text{ onde } j = 1,2,...,K. \]
Não menos importante, a função relu é amplamente utilizada nas camadas ocultas (antes da última), pois ela é não-linear e de fácil utilização pois só recebe valores positivos e é igual a reta afim para tais valores. Esta função de ativação é bastante útil pois facilita a computação, contudo, esta também apresenta alguns problemas de estimação com descidas de gradientes que tendem à zero devido à problemas de máquina (leia-se métodos númericos).
\[ f(x) = máx(0,x) \]
O próximo passo é definir a função de perda, o método de estimação e a métrica de performance. Para tal utilize compile. Para a função de perda vamos utilizar a entropia cruzada para variáveis categoricas. Quanto ao método de otimização para estimar os parâmetros, utilizaremos o método RMSProp. Por fim, a métrica de performance será a acúracia.
rnn_model %>% compile(
loss = 'categorical_crossentropy', #categorical_accuracy
optimizer = optimizer_rmsprop(),
metrics = c('accuracy')
)Agora vamos treinar o modelo. Veja que estamos salvando o processo a seguir em um objeto. Este objeto irá guardar a trajetória de estimação do modelo para que possamos ver a função de perda e a acúracia. Função de perda é o que será minimizado, geralmente minimiza-se a acúracia.
Para treinar sua rede, utilize fit. Nessa função acrescente a matriz de treino e o vetor resposta (neste caso matriz), como também o número de validações cruzadas (epochs) e o tamanho de cada validação cruzada (batch_size). Além disso, podemos definir aleatóriamente uma parte da nossa matriz de treinamento para fazer as validações cruzadas por meio do argumento validation_split. Todas esta preocupação com overfitting é muito importante pois estamos trabalhando com um imenso número de parâmetros.
history <- rnn_model %>% fit(
x_train, y_train,
epochs = 10, batch_size = 128,
validation_split = 0.2)
plot(history) Testando o modelo
Após treinamento, podemos agora ver a acúracia de nosso modelo na base de testes. basta utilizar a função evaluate dando como argumentos a matriz de teste e a matriz de respostas.
## $loss
## [1] 0.09737537
##
## $acc
## [1] 0.9745722
Vamos agora fazer o mesmo que a função evaluate faz só que manualmente =). Basta ter as matrizes de pesos de cada camada e fazer as operações matriciais.
## [1] "list"
## [1] 6
## [1] 784 256
Input_bias <- wgts[[2]]
Layer <- wgts[[3]]
Layer_bias <- wgts[[4]]
Output <- wgts[[5]]
Output_bias <- wgts[[6]]
Y = x_test[4039,] %*% Input + t(Input_bias)
Y_relu = Y * (Y > 0) # Activation Function if_else(Y > 0, Y, 0)
Z = Y_relu %*% Layer + t(Layer_bias)
Z_relu = Z * (Z > 0) # Activation Function
W = Z_relu %*% Output + t(Output_bias)
f_exp = exp(W)
W_softmax = f_exp/sum(f_exp)# softmax output
round(W_softmax,5)## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,] 0 0 0 0 6e-05 0 0 0 0 0.99994
Note que o output são probabilidades para cada digito. O digito (cada coluna é um digito) com a maior probabilidade será escolhido para a predição. Para nosso exemplo o digito da base de teste foi o 9 (coluna 10). O modelo estimou uma prob. de 99.9999% de ser este digito. Acertamos em cheio !
Teste com dados reais
Agora vamos brincar um pouco com nosso modelo. Vamos testar se o mesmo funciona na prática. Para carregar a imagem utilizar o pacote imager.
suppressMessages(suppressWarnings(library(imager)))
digit <- load.image("digit2.png")
plot(digit,axes = FALSE)digit28 <- digit %>% resize(size_x = 28, size_y = 28, interpolation_type = 1L)
digit28 <- rowMeans(digit28, dims = 2)
digit28 %>%
apply(1, rev) %>%
t() %>%
image(col = grey.colors(256), axes = FALSE)# Deixando no intervalo [0,1]
digit_test_2 <- digit28 %>%
apply(2,function(x) x/255) %>%
apply(1, rev) %>%
t() %>%
as.vector()
# Prevendo
Y = (1 - digit_test_2) %*% Input + t(Input_bias)
Y_relu = Y * (Y > 0) # Activation Function
Z = Y_relu %*% Layer + t(Layer_bias)
Z_relu = Z * (Z > 0) # Activation Function
W = Z_relu %*% Output + t(Output_bias)
f_exp = exp(W)
W_softmax = f_exp/sum(f_exp)# softmax output
round(W_softmax, 5)## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,] 0 0 1 0 0 0 0 0 0 0
Veja que o nosso modelo estimou uma probabilidade de 100% de ser o número 2, que foi oque desenhamos. O modelo realmente é muito bom.