Perceptron Múltiplas Camadas
Perceptron Multiplas Camadas
O perceptron multicamadas é a combinação de vários neurônios artificiais organizados em camadas. A ideia principal é, partindo de uma rede perceptron de um único neurônio, e em vez de “imputar” os valores de cováveis neste neurônio vão entrar as saídas de outras rede perceptron de camada única.
Vamos começar com o perceptron de camada única. Supondo 3 variáveis de entrada, são 4 parâmetros desconhecidos para serem estimados.
Suponha agora que em vez desse único neurônio receber os valores das 3 covariáveis da base ele receba as saídas de outros perceptrons de camada única, e estes sim são alimentados pelos valores das covariáveis. A rede, que antes tinha apenas um neurônio na camada de saída, agora possui uma camada oculra com mais 3 neurônios, total de 4 neurônios na rede. Esta rede possui 16 parâmetros desconhecidos.
A camada oculta pode ter quantos neurônios a gente quiser. O exemplo da figura abaixo apresenta uma rede com uma camada oculta com 4 neurônios, que resulta em 21 parâmetros desconhecidos.
Também podemos incluir quantas novas camadas quisermos. Para o exemplo da figura abaixo, a rede possui 2 camadas ocultas, a primeira com 4 neurônios e a segunda com 3. Para esta rede temos 31 parâmetros desconhecidos.
A Arquitetura
Em comparação com o Perceptron camada única, a arquitetura dos perceptrons de múltiplas camadas são bem mais complexas.
Chamamos de camada de entrada a camada da rede representada pelas covariáveis, que é entendida como uma camada visível. Chamamos de camada de saída a última camada, com o(s) neurônio(s) de saída (já explico o caso de mais de um neurônio na saída), que também é visível. Por fim, chamamos de camadas ocultas todas as outras camadas da rede.
O número de parâmetros desconhecidos da rede depende do tamanho e da arquitetura da rede. Quanto mais camadas ocultas, mais parâmetros teremos e mais complexo o modelo é. É a complexidade das redes que capturam melhor padrões não lineares dos dados.
O Modelo Matemático
O modelo matemático, que define \(\hat{y}\) como função das covariáveis, considerando valores para os parâmetros, também complica muito com a inclusão de camadas ocultas e mais neurônios.
Primeiro, vejamos como é o modelo matemático para a rede neural Perceptron Camada Única.
\[ \hat{y}_i = g(w_1x_{i,1} + w_2x_{i,2} + w_3x_{i,3} + \Theta) \] Se colocarmos uma camada oculta com três neurônios a função que retorna o valor de \(\hat{y}_i\) em termos das covariáveis muda.
\[ \begin{aligned} \hat{y}_i = g\Big( &w_{10}g(w_1x_{i,1} + w_4x_{i,2} + w_7x_{i,3} + \Theta_1) \\ + \ &w_{11}g(w_2x_{i,1} + w_5x_{i,2} + w_8x_{i,3} + \Theta_2) \\ + \ &w_{12}g(w_3x_{i,1} + w_6x_{i,2} + w_9x_{i,3} + \Theta_3) + \Theta_4\Big) \end{aligned} \] Para essa expressão considerei a numeração dos pesos \(w\) de forma sequencial, da esquerda para a direita, de cima para baixo. O mesmo para a numeração dos limiares aditivos \(\Theta\).
Para complicar ainda mais, vejamos o caso de 2 camadas ocultas, sendo a primeira com 4 e a segunda com 3 neurônios.
\[ \begin{aligned} \hat{y}_i = g\Big( w_{25} g\Big( &w_{13} g(w_1x_{i,1} + w_5x_{i,2} + w_9x_{i,3} + \Theta_1) \\ + \ &w_{16} g(w_2x_{i,1} + w_6x_{i,2} + w_{10}x_{i,3} + \Theta_2) \\ + \ &w_{19} g(w_3x_{i,1} + w_7x_{i,2} + w_{11}x_{i,3} + \Theta_3) \\ + \ &w_{22} g(w_4x_{i,1} + w_8x_{i,2} + w_{12}x_{i,3} + \Theta_4) + \Theta_5\Big) \\ + w_{26}g\Big( &w_{14} g(w_1x_{i,1} + w_5x_{i,2} + w_9x_{i,3} + \Theta_1) \\ + \ &w_{17} g(w_2x_{i,1} + w_6x_{i,2} + w_{10}x_{i,3} + \Theta_2) \\ + \ &w_{20} g(w_3x_{i,1} + w_7x_{i,2} + w_{11}x_{i,3} + \Theta_3) \\ + \ &w_{23} g(w_4x_{i,1} + w_8x_{i,2} + w_{12}x_{i,3} + \Theta_4) + \Theta_6\Big) \\ + w_{27}g\Big( &w_{15} g(w_1x_{i,1} + w_5x_{i,2} + w_9x_{i,3} + \Theta_1) \\ + \ &w_{18} g(w_2x_{i,1} + w_6x_{i,2} + w_{10}x_{i,3} + \Theta_2) \\ + \ &w_{21} g(w_3x_{i,1} + w_7x_{i,2} + w_{11}x_{i,3} + \Theta_3) \\ + \ &w_{24} g(w_4x_{i,1} + w_8x_{i,2} + w_{12}x_{i,3} + \Theta_4) + \Theta_7\Big) + \Theta_8\Big) \end{aligned} \]
Com isso podemos imaginar o quanto complexo seria tentar minimizar a função de custo a partir do gradiente descendente. Precisaríamos, por exemplo, derivar essa função em termos de cada parâmetro desconhecido. Isso não será possível e será necessário outro método iterativo. A forma de estimar os parâmetros será vista na próxima aula. Antes disso, vejamos um exemplo de como treinar no R uma rede Perceptron com camadas ocultas.
Um exemplo de regressão
Vamos continuar o exemplo dos dados de seguro. O objetivo é o mesmo de antes: criar um modelo de redes neurais para prever o gasto de um segurado. Vamos considerar a base de treino já preparada em aulas anteriores (NAs excluídas, variáveis quantitativas padronizados e qualitativas transformadas em binárias/indicadoras).
Vamos aqui comparar diferentes arquiteturas com todas as variáveis menos as de região. Vamos usar também a função de ativação logística, por isso devemos usar a base onde a variável resposta foi transformada para o intervalo \([0,1]\).
library(tidyverse)
library(neuralnet)
base_treino_final= readRDS("base_treino_final_log.RDS")Vamos chamar de modelo_0 o modelo sem camada oculta.
modelo_0 = neuralnet(
formula = charges ~ age + sexmale + bmi + children + smokeryes,
data = base_treino_final,
hidden = 0,
linear.output = FALSE)
plot(modelo_0,rep = "best")Vamos criar também três modelos com 1 camada oculta, o primeiro contendo 1 neurônio, o segundo contendo 2 neurônios e o terceiro com 3 neurônios, chamados de modelo_1, modelo_2 e modelo_3, respectivamente. Para isso vamos mudar o argumento hidden da função neuralnet.
modelo_1 = neuralnet(
formula = charges ~ age + sexmale + bmi + children + smokeryes,
data = base_treino_final,
hidden = 1,
linear.output = FALSE)
plot(modelo_1,rep = "best")modelo_2 = neuralnet(
formula = charges ~ age + sexmale + bmi + children + smokeryes,
data = base_treino_final,
hidden = 2,
linear.output = FALSE)
plot(modelo_2,rep = "best")modelo_3 = neuralnet(
formula = charges ~ age + sexmale + bmi + children + smokeryes,
data = base_treino_final,
hidden = 3,
linear.output = FALSE)
plot(modelo_3,rep = "best")Criamos também um modelo com 2 camadas ocultas contendo 3 neurônios na primeira e 2 na segunda, que será chamado de modelo_32. Para isso vamos mudar o argumento hidden da função neuralnet.
modelo_32 = neuralnet(
formula = charges ~ age + sexmale + bmi + children + smokeryes,
data = base_treino_final,
hidden = c(3,2),
linear.output = FALSE)
plot(modelo_32,rep = "best")Por fim, Criamos um modelo com 3 camadas ocultas contendo 3 neurônios em cada, que será chamado de modelo_33. Para isso vamos mudar o argumento hidden da função neuralnet.
modelo_333 = neuralnet(
formula = charges ~ age + sexmale + bmi + children + smokeryes,
data = base_treino_final,
hidden = c(3,3,3),
linear.output = FALSE)
plot(modelo_333,rep = "best")A única diferença entre os três modelos criados é a arquitetura.
Vejamos os valores de \(R^2\) na base de treino para cada um dos três modelos. Para isso, primeiro guardamos os valores previstos:
y_0_ = modelo_0$net.result[[1]]
y_1_ = modelo_1$net.result[[1]]
y_2_ = modelo_2$net.result[[1]]
y_3_ = modelo_3$net.result[[1]]
y_32_ = modelo_32$net.result[[1]]
y_333_ = modelo_333$net.result[[1]]Depois fazemos a transformação inversa para voltar a variável resposta á sua unidade valor original. Para isso vamos precisar da base de treino antes da transformação das variáveis.
base_treino= readRDS("base_treino.RDS")
min = min(base_treino$charges)
max = max(base_treino$charges)
y_0 = y_0_ * (max - min) + min
y_1 = y_1_ * (max - min) + min
y_2 = y_2_ * (max - min) + min
y_3 = y_3_ * (max - min) + min
y_32 = y_32_ * (max - min) + min
y_333 = y_333_ * (max - min) + minEm seguida qualculamos os erros na previsão de cada observação.
erro_0 = y_0 - base_treino$charges
erro_1 = y_1 - base_treino$charges
erro_2 = y_2 - base_treino$charges
erro_3 = y_3 - base_treino$charges
erro_32 = y_32 - base_treino$charges
erro_333 = y_333 - base_treino$chargesCalculamos então SSE:
sse_0 = sum((erro_0)^2)
sse_1 = sum((erro_1)^2)
sse_2 = sum((erro_2)^2)
sse_3 = sum((erro_3)^2)
sse_32 = sum((erro_32)^2)
sse_333 = sum((erro_333)^2)E por fim o \(R^2\):
sst = sum((base_treino$charges - mean(base_treino$charges))^2)
(R2_0 = 1 - sse_0/sst)[1] 0.7529882
(R2_1 = 1 - sse_1/sst)[1] 0.7746513
(R2_2 = 1 - sse_2/sst)[1] 0.8529637
(R2_3 = 1 - sse_3/sst)[1] 0.8595364
(R2_32 = 1 - sse_32/sst)[1] 0.8479433
(R2_333 = 1 - sse_333/sst)[1] 0.8541939
Vejamos agora como os modelos se comportam na base de teste.
base_teste= readRDS("base_teste.RDS")
age_teste = (base_teste$age - mean(base_treino$age))/sd(base_treino$age)
bmi_teste = (base_teste$bmi - mean(base_treino$bmi))/sd(base_treino$bmi)
children_teste = (base_teste$children - mean(base_treino$children))/sd(base_treino$children)Agora vamos criar a base de teste transformada (sem a variável resposta - charge) e realizar o tratamento nas variáveis categóricas.
base_teste_ = tibble(
age = age_teste,
bmi = bmi_teste,
children = children_teste,
sex = base_teste$sex,
smoker = base_teste$smoker,
region = base_teste$region
)
base_teste_final = model.matrix( ~ age + sex + bmi + children + smoker + region,
data = base_teste_)
base_teste_final = base_teste_final[,-1]É importante que as variáveis categóricas criadas pela função model.matrix sejam as mesmas que foram criadas para a base de treino. Vamos verificar.
colnames(base_treino_final)[1] "age" "sexmale" "bmi" "children"
[5] "smokeryes" "regionnorthwest" "regionsoutheast" "regionsouthwest"
[9] "charges"
colnames(base_teste_final)[1] "age" "sexmale" "bmi" "children"
[5] "smokeryes" "regionnorthwest" "regionsoutheast" "regionsouthwest"
Agora a base de teste está pronta para realizarmos as previsões, que virão na unidade transformada.
pred_teste_0_ = predict(modelo_0,newdata = base_teste_final)
pred_teste_1_ = predict(modelo_1,newdata = base_teste_final)
pred_teste_2_ = predict(modelo_2,newdata = base_teste_final)
pred_teste_3_ = predict(modelo_3,newdata = base_teste_final)
pred_teste_32_ = predict(modelo_32,newdata = base_teste_final)
pred_teste_333_ = predict(modelo_333,newdata = base_teste_final)E podemos retornar a previsão da variável resposta para a unidade original.
pred_teste_0 = pred_teste_0_ * (max - min) + min
pred_teste_1 = pred_teste_1_ * (max - min) + min
pred_teste_2 = pred_teste_2_ * (max - min) + min
pred_teste_3 = pred_teste_3_ * (max - min) + min
pred_teste_32 = pred_teste_32_ * (max - min) + min
pred_teste_333 = pred_teste_333_ * (max - min) + minerro_teste_0 = pred_teste_0 - base_teste$charges
erro_teste_1 = pred_teste_1 - base_teste$charges
erro_teste_2 = pred_teste_2 - base_teste$charges
erro_teste_3 = pred_teste_3 - base_teste$charges
erro_teste_32 = pred_teste_32 - base_teste$charges
erro_teste_333 = pred_teste_333 - base_teste$chargesCalculamos então SSE:
sse_teste_0 = sum((erro_teste_0)^2)
sse_teste_1 = sum((erro_teste_1)^2)
sse_teste_2 = sum((erro_teste_2)^2)
sse_teste_3 = sum((erro_teste_3)^2)
sse_teste_32 = sum((erro_teste_32)^2)
sse_teste_333 = sum((erro_teste_333)^2)E por fim o \(R^2\):
sst_teste = sum((base_teste$charges - mean(base_treino$charges))^2)
(R2_teste_0 = 1 - sse_teste_0/sst_teste)[1] 0.778108
(R2_teste_1 = 1 - sse_teste_1/sst_teste)[1] 0.7956841
(R2_teste_2 = 1 - sse_teste_2/sst_teste)[1] 0.8567615
(R2_teste_3 = 1 - sse_teste_3/sst_teste)[1] 0.8546028
(R2_teste_32 = 1 - sse_teste_32/sst_teste)[1] 0.8447543
(R2_teste_333 = 1 - sse_teste_333/sst_teste)[1] 0.8421678
barplot(matrix(c(R2_0,R2_1,R2_2,R2_3,R2_32,R2_333,
R2_teste_0,R2_teste_1,R2_teste_2,R2_teste_3,R2_teste_32,R2_teste_333),nrow = 2,byrow = T),
beside = T,
names.arg = c("0","1","2","3","32","333"),
ylim = c(0,1),
col=c("tomato","blue4"),
args.legend = list(x = "bottomright"),
legend.text = c("treino","teste"))
abline(h=R2_0,lty=2,col="lightgray")
abline(h=R2_3,lty=2,col="lightgray")