Bayesian Neural Network

Autor do notebook: Felipe Maia Polo

Nesta implementação seguiremos em parte a proposta apresentada no livro: Bishop, C. M. (2006). Pattern recognition and machine learning. Springer Science+ Business Media.. Assim, quando citar o autor do livro, estarei me referindo ao Bishop.

Abrindo as bibliotecas necessárias

In [127]:
library(MASS)
library(e1071)
library(ggplot2)
library(sigmoid)

Gerando dados artificiais

In [128]:
n0 <- 1200
n1 <- 1200

X0 <- rbind(mvrnorm(n = n0/3, mu=c(1,2), matrix(c(2,-1,-1,1),2,2)), 
            mvrnorm(n = n0/3, mu=c(-3,-1), matrix(c(2,-1,-1,1),2,2)),
            mvrnorm(n = n0/3, mu=c(-2,2), matrix(c(2,1,1,1),2,2)))
X1 <- mvrnorm(n = n1, mu=c(-0.5,0), matrix(c(1.5,0,0,1),2,2))

y0 <- rep(0,n0)
y1 <- rep(1,n1)

X <- rbind(X0,X1)
y <- c(y0,y1)

Plotando os dados:

In [129]:
data <- as.data.frame(cbind(X, y))
names(data) <- c('x1','x2','y')

qplot(x1, x2, colour = y, data = data)

No modelo proposto pelo autor, temos a seguinte função de verossimilhança:

\begin{align} p(\mathbf{y}|\mathbf{x},\mathbf{w})=\prod_{i=1}^{n}p(y_i|x_i,\mathbf{w})=\prod_{i=1}^{n}h(x_i,\mathbf{w})^{y_i}(1-h(x_i,\mathbf{w}))^{(1-y_i)} \end{align}

Sendo que $h(.)$ é uma rede neural com uma camada oculta da seguinte forma:

\begin{align} h(\mathbf{x_i},\mathbf{w})=\sigma \Big (\mathbf{z_{2i}}^\top \mathbf{w_2}\Big)=\sigma_i \end{align}\begin{align} \mathbf{z_{2i}}=\begin{bmatrix}1\\ f\big(\mathbf{z_{1i}}^\top \mathbf{W_1} \big)^\top \end{bmatrix} \end{align}\begin{align} \mathbf{z_{1i}} = \begin{bmatrix}1\\ \mathbf{x_i}\end{bmatrix} \end{align}

Onde $f(.)$ é uma função não linear aplicada coordenada a coordenada. Nesse caso, vamos assumir que $f(.)=\text{max}(.,0)=\text{ReLU}(.)$, Veja que internalizamos o intercepto (bias) nos próprios vetores, o que facilitará as contas daqui para frente. Acima temos que as equações foram escritas para o indivíduo $i$, no entanto, codificaremos as funções já pensando na base inteira, o que deixará nosso algoritmo mais eficiente - pensaremos em $X$ como uma matriz em que cada linha diz respeito a um indivíduo. Definindo as funções:

In [130]:
sig <- function(z){return(1/(1+exp(-z)))}

f <- function(z){return(apply(z,2,relu))}

itc <- function(Z){return(cbind(rep(1,NROW(Z)),Z))}

h <- function(Z,V1,v2){return(sig(itc(f(itc(Z)%*%V1))%*%v2))}

Definimos então a distribuição a priori dos pesos da rede neural da seguinte maneira

\begin{align} \mathbf{W} |\alpha\sim N(0,\alpha^{-1} I_k) \end{align}

Sendo $\alpha$ um hiperparâmetro de precisão. Como queremos fixar uma prior pouco informativa, vamos assumir que $\alpha=\frac{1}{100}$. Abaixo vamos simular previsões feitas pela rede se nenhum aprendizado:

In [131]:
#Tamanho da amostra
n <- NROW(X)

#Número de variáveis
d <- NCOL(X)

#Número de neurônios na camada oculta
k1=5

#Número de neurônios na camada de saída
k2=1

#Alpha
alpha=1

Predizendo a probabilidade para os 5 primeiros indivíduos

In [132]:
iter <- 1000
simul <- c()

mu1 <- rep(0,(d+1)*k1)
Sigma1 <- alpha*diag((d+1)*k1)

mu2 <- rep(0,(k1+1)*k2)
Sigma2 <- alpha*diag((k1+1)*k2)

for(i in 1:iter){
    #Amostrando W1
    W1 <- matrix(mvrnorm(1, mu1, Sigma1), nrow = (d+1), byrow = F) #cada coluna diz respeito a uma variável de entrada

    #Amostrando w2
    w2 <- mvrnorm(1, mu2, Sigma2)
    
    #Rbind
    simul <- cbind(simul, h(X,W1,w2))
    }

Visualizando $\hat{P}(Y_1=1|\mathbf{x_1},\mathbf{w},\alpha)$, sendo $\mathbf{w}$ realizado de acordo com sua priori:

In [133]:
dim(simul)
  1. 2400
  2. 1000
In [134]:
hist(simul[1,],main='Histograma da probabilidade de sucesso para o indivíduo 1')

Voltando à teoria, temos a seguinte proporcionalidade pelo teorema de Bayes:

\begin{align} p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha) \propto p(\mathbf{y}|\mathbf{x},\mathbf{w},\alpha)p(\mathbf{w}|\alpha) \end{align}

Tirando o logaritmo da posteriori, temos que:

\begin{align} \text{ln} p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha)&=\text{ln}p(\mathbf{y}|\mathbf{x},\mathbf{w},\alpha)+\text{ln}p(\mathbf{w}|\alpha) +\text{cte} \\ &=\text{ln}\prod_{i=1}^{n}p(y_i|x_i,\mathbf{w})+\text{ln}\prod_{j}p(\mathbf{w}_j|\alpha) +\text{cte} \\ &=\text{ln}\prod_{i=1}^{n}h(x_i,\mathbf{w})^{y_i}(1-h(x_i,\mathbf{w}))^{(1-y_i)}+\text{ln}\prod_{j}\sqrt{\frac{\alpha}{2 \pi}} \text{exp}\Big(-\frac{\alpha(w_j^2)}{2}\Big) +\text{cte} \\ &=\sum_{i=1}^{n}{y_i}\text{ln}h(x_i,\mathbf{w})+(1-y_i)\text{ln}(1-h(x_i,\mathbf{w}))-\frac{\alpha}{2}\left \| \mathbf{w} \right \|^2 +\text{cte} \\ \end{align}

Sendo que $\circ$ é o produto elemento a elemento de matrizes. Como $p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha)$ assume uma forma muito complicada, aproximaremos essa distribuição da maneira que segue. Primeiramente, aproximaremos o logaritmo da posteriori utilizando o polinômio de Taylor de segunda ordem centrado em $\mathbf{w}_{\text{MAP}}=\underset{\mathbf{w}}{\text{argmax}}~p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha)=\underset{\mathbf{w}}{\text{argmax}}~\text{ln}p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha)$:

\begin{align} \text{ln}p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha) &\approx \text{ln}p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)+ (\mathbf{w}-\mathbf{w}_{\text{MAP}})^\top\nabla \text{ln}p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)-\frac{1}{2}(\mathbf{w}-\mathbf{w}_{\text{MAP}})^\top A(\mathbf{w}-\mathbf{w}_{\text{MAP}}) \\ &= p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)-\frac{1}{2}(\mathbf{w}-\mathbf{w}_{\text{MAP}})^\top A(\mathbf{w}-\mathbf{w}_{\text{MAP}}) \end{align}

Sendo que $A=-\frac{\partial ^2 }{\partial \mathbf{w}^2}\text{ln}p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)$ é o negativo da matriz Hessiana do log da posteriori no ponto $\mathbf{w}=\mathbf{w}_{\text{MAP}}$. Tirando o exponencial dos dois lados, temos:

\begin{align} p(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha) &\approx p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)*\text{exp}\Big(-\frac{1}{2}(\mathbf{w}-\mathbf{w}_{\text{MAP}})^\top A(\mathbf{w}-\mathbf{w}_{\text{MAP}})\Big) \\ &= c(\mathbf{w}_{\text{MAP}}) *\text{exp}\Big(-\frac{1}{2}(\mathbf{w}-\mathbf{w}_{\text{MAP}})^\top A(\mathbf{w}-\mathbf{w}_{\text{MAP}})\Big) \end{align}

É possível visualizar o núcleo da distirbuição normal na fórmula acima e, assim, é natural pensar que uma possível aproximação para a posteriori é a seguinte distribuição:

\begin{align} q(\mathbf{w}|\mathbf{y},\mathbf{x},\alpha)=N(\mathbf{w}_{\text{MAP}},\mathbf{A}^{-1}) \end{align}

Primeiramente, realizaremos o cálculo para $\mathbf{w}_{\text{MAP}}$ e depois para a matriz $A$. Realizaremos o cálculo de $\mathbf{w}_{\text{MAP}}$ achando o argumento que maximiza

\begin{align} g(\mathbf{w})=\sum_{i=1}^{n}{y_i}\text{ln}h(x_i,\mathbf{w})+(1-y_i)\text{ln}(1-h(x_i,\mathbf{w}))-\frac{\alpha}{2}\left \| \mathbf{w} \right \|^2 \end{align}

Para tal feito, teremos que recorrer ao algoritmo do Stochastic Grandient Descent e backpropagation. Veja que $\mathbf{w}$ pode ser particionado em $\mathbf{w_1}$ e em $\mathbf{w_2}$ e que $\nabla_{\mathbf{w}_1} g(\mathbf{w})$ depende de $\mathbf{w_2}$ enquanto $\nabla_{\mathbf{w}_2} g(\mathbf{w})$ não depende de $\mathbf{w}_1$. Calcularemos então o segundo gradiente em primeiro lugar:

\begin{align} \nabla_{\mathbf{w}_2} g(\mathbf{w})&= \sum_{i=1}^{n}\Bigg[{y_i}\nabla_{\mathbf{w}_2}\text{ln}h(x_i,\mathbf{w})+(1-y_i)\nabla_{\mathbf{w}_2}\text{ln}(1-h(x_i,\mathbf{w}))\Bigg]-\frac{\alpha}{2}\nabla_{\mathbf{w}_2}\left \| \mathbf{w} \right \|^2 \\ &= \sum_{i=1}^{n}\Bigg[{y_i}\nabla_{\mathbf{w}_2}\text{ln}\sigma \big ( \mathbf{z_{2i}}^\top \mathbf{w_2} \big)+(1-y_i)\nabla_{\mathbf{w}_2}\text{ln}\Big(1-\sigma \big (\mathbf{z_{2i}}^\top \mathbf{w_2} \big)\Big)\Bigg]-\alpha \mathbf{w}_2, \text{ adotando } \sigma_i=\sigma \big (\mathbf{z_{2i}}^\top \mathbf{w_2} \big) \\ &= \sum_{i=1}^{n}\Bigg[y_i(1-\sigma_i)\mathbf{z}_{2i}+(1-y_i)\sigma_i\mathbf{z}_{2i}\Bigg]-\alpha \mathbf{w}_2 \\ &= \sum_{i=1}^{n}\Bigg[(y_i-\sigma_i)\mathbf{z}_{2i}\Bigg]-\alpha \mathbf{w}_2 \\ &=\begin{bmatrix} \mathbf{z}_{2,1} & ...& \mathbf{z}_{2,n}\end{bmatrix}(\mathbf{y}-\boldsymbol{\sigma})-\alpha \mathbf{w}_2\\ &=\mathbf{Z}_2(\mathbf{y}-\boldsymbol{\sigma})-\alpha \mathbf{w}_2 \\ \end{align}

Antes de partirmos para o cálculo em si, é necessário frizar que $W_1$ é uma matriz bidimensional (de ordem $d+1$ X $k_1$). Para facilitar o cálculo vamos ver a matriz como uma concatenação de vetores da seguinte forma $\mathbf{W}_1=\begin{bmatrix}\mathbf{w}_{1,1} & ... & \mathbf{w}_{1,k_1}\end{bmatrix}$, sendo que representaremos o jacobiano da seguinte maneira $\frac{\partial }{\partial \mathbf{W}_1} g(\mathbf{w})=\begin{bmatrix}\frac{\partial }{\partial \mathbf{w}_{1,1}} g(\mathbf{w}) & ... & \frac{\partial }{\partial \mathbf{w}_{1,k_1}} g(\mathbf{w})\end{bmatrix}$. Botando a mão na massa:

\begin{align} \nabla_{\mathbf{w}_{1,t}} g(\mathbf{w})&= \nabla_{\mathbf{w}_{1,t}} g(\mathbf{w})= \nabla_{\mathbf{w}_{1,t}} \Bigg[\sum_{i=1}^{n}{y_i}\text{ln}h(x_i,\mathbf{w})+(1-y_i)\text{ln}(1-h(x_i,\mathbf{w}))-\frac{\alpha}{2}\left \| \mathbf{w} \right \|^2\Bigg] \\ &= \nabla_{\mathbf{w}_{1,t}} \Bigg[\sum_{i=1}^{n}{y_i}\text{ln}h(x_i,\mathbf{w})+(1-y_i)\text{ln}(1-h(x_i,\mathbf{w}))\Bigg]-\frac{\alpha}{2}\nabla_{\mathbf{w}_{1,t}} \left \| \mathbf{w} \right \|^2 \\ &= \sum_{i=1}^{n}\Bigg[{y_i} \nabla_{\mathbf{w}_{1,t}} \text{ln}h(x_i,\mathbf{w})+(1-y_i)\nabla_{\mathbf{w}_{1,t}}\text{ln}(1-h(x_i,\mathbf{w}))\Bigg] -\alpha \mathbf{w}_{1,t} \\ &=\sum_{i=1}^{n}\Bigg[{y_i} \frac{\partial \text{ln}h(x_i,\mathbf{w})}{\partial \sigma_i}\frac{\partial \sigma_i}{\partial \mathbf{w}_{1,t}} +(1-y_i) \frac{\partial \text{ln}\big(1-h(x_i,\mathbf{w})\big)}{\partial \sigma_i}\frac{\partial \sigma_i}{\partial \mathbf{w}_{1,t}}\Bigg] -\alpha \mathbf{w}_{1,t} , \text{ adotando } \sigma_i=\sigma \big (\mathbf{w_2}^T \mathbf{z_{2i}} \big) \\ &=\sum_{i=1}^{n}\Bigg[{y_i} (1-\sigma_i) \frac{\partial \mathbf{z}_{2i}^\top\mathbf{w}_2}{\partial \mathbf{w}_{1,t}} -(1-y_i) \sigma_i \frac{\partial \mathbf{z}_{2i}^\top\mathbf{w}_2}{\partial \mathbf{w}_{1,t}}\Bigg] -\alpha \mathbf{w}_{1,t} \\ &=\sum_{i=1}^{n}\Bigg[(y_i-\sigma_i) \frac{\partial \mathbf{z}_{2i}^\top\mathbf{w}_2}{\partial \mathbf{w}_{1,t}} \Bigg] -\alpha \mathbf{w}_{1,t}=\bigstar \\ \end{align}

Vamos calcular a derivada que sobrou antes de continuarmos a resolução

\begin{align} \frac{\partial \mathbf{z}_{2i}^\top\mathbf{w}_2}{\partial \mathbf{w}_{1,t}}=\frac{\partial }{\partial \mathbf{w}_{1,t}} \Big[w_{2,0} + \sum^{k_1}_{j=1}w_{2,j}f(\mathbf{z}_{1i}^\top \mathbf{w}_{1,j}) \Big]=w_{2,t}\frac{\partial }{\partial \mathbf{w}_{1,t}}f(\mathbf{z}_{1i}^\top \mathbf{w}_{1,t})=w_{2,t}\delta_{it}\mathbf{z}_{1i} \end{align}

Sendo que $\delta_{it}=\mathbb{I}(\mathbf{z}_{1i}^\top \mathbf{w}_{1,t}>0)$ é uma indicadora se o argumento é positivo. Voltando:

\begin{align} \bigstar&=w_{2,t} \Bigg[\sum_{i=1}^{n}(y_i-\sigma_i) \delta_{it} \mathbf{z}_{1i}\Bigg] -\alpha \mathbf{w}_{1,t} \\ &=w_{2,t}\begin{bmatrix} \mathbf{z}_{1,1} & ...& \mathbf{z}_{1,n}\end{bmatrix}\big[(\mathbf{y}-\boldsymbol{\sigma}) \circ \boldsymbol{\delta_t}\big]-\alpha \mathbf{w}_{1,t}\\ &=w_{2,t}\mathbf{Z}_1\big[(\mathbf{y}-\boldsymbol{\sigma}) \circ \boldsymbol{\delta_t}\big]-\alpha \mathbf{w}_{1,t} \\ \end{align}

Logo,

\begin{align} \frac{\partial }{\partial \mathbf{W}_1} g(\mathbf{w})&=\begin{bmatrix}w_{2,1}\mathbf{Z}_1\big[(\mathbf{y}-\boldsymbol{\sigma}) \circ \boldsymbol{\delta_1}\big]-\alpha \mathbf{w}_{1,1} & ... & w_{2,k_1}\mathbf{Z}_1\big[(\mathbf{y}-\boldsymbol{\sigma}) \circ \boldsymbol{\delta_{k_1}}\big]-\alpha \mathbf{w}_{1,k_1}\end{bmatrix} \\ &=\mathbf{Z}_1 \Big[\begin{bmatrix}w_{2,1}(\mathbf{y}-\boldsymbol{\sigma})& ... & w_{2,k_1}(\mathbf{y}-\boldsymbol{\sigma})\end{bmatrix}\circ \boldsymbol{\delta} \Big]-\alpha \mathbf{W}_{1} \\ &=\mathbf{Z}_1 \Big[(\mathbf{y}-\boldsymbol{\sigma})\mathbf{w}^\top_2\circ \boldsymbol{\delta} \Big]-\alpha \mathbf{W}_{1}, ~ \boldsymbol{\delta}=(\delta_{it}) \text{ para } i \in \{1,...,n\} \text{ e } t \in \{1,...,k_1\}\\ \end{align}

Definindo função que queremos maximizar:

In [135]:
g <- function(w,Z,V1,v2,alpha){return(sum(log(cbind(1-h(Z,V1,v2),h(Z,V1,v2)))*cbind(0^w,w))-alpha*(sum(v2^2)+sum(V1^2)))}

Definindo os nossos gradientes:

In [136]:
grad2<-function(y,X,W1,w2,alpha){return(t(itc(f(itc(X)%*%W1)))%*%(y-h(X,W1,w2))-alpha*w2)}

grad1<-function(y,X,W1,w2,alpha){
    delta=1*(itc(X)%*%W1>0)
    return(t(itc(X))%*%((y-h(X,W1,w2))%*%t(w2[2:(k1+1)])*delta)-alpha*W1)
}

Apesar de acharmos que as contas que fizemos estão certas, é sempre bom fazer uma comparação de um resultado numérico e do nosso resultado analítico. Fixando $W_1$ e $w_2$, vamo fazer as razões dos gradientes numéricos e analíticos. Primeiramente vamos definir as funções que calculam os gradientes numéricos:

In [137]:
grad2_check<-function(){    
    d=10^(-10)
    grad2_num=rep(0,length(w2))

    for(i in 1:length(w2)){
        u=rep(0,length(w2))
        u[i]=d
        grad2_num[i]=(g(y,X,W1,w2+u,alpha)-g(y,X,W1,w2-u,alpha))/(2*d)
    } 
    return(grad2_num)
}

grad1_check<-function(){
    d=10^(-10)
    grad1_num=matrix(rep(0,prod(dim(W1))),NROW(W1),NCOL(W1))

    for(i in 1:NROW(W1)){
        for(j in 1:NCOL(W1)){
            u=matrix(rep(0,prod(dim(W1))),NROW(W1),NCOL(W1))
            u[i,j]=d
            grad1_num[i,j]=(g(y,X,W1+u,w2,alpha)-g(y,X,W1-u,w2,alpha))/(2*d)
        }
    }  
    return(grad1_num)
}

Vamos efetuar as razões agora:

In [138]:
grad2_check()/grad2(y,X,W1,w2,alpha)

grad1_check()/grad1(y,X,W1,w2,alpha)
0,9102832
1,0067890
0,9232554
0,9991374
0,9831350
0,8993813
0,99898690,93381131,01404360,98862340,9815318
1,00207230,82366870,98200930,99739850,9095436
0,99555940,54084651,03264951,00350950,9219060

É possível ver que as razões todas se aproximam de 1, o que nos dá indícios de que nossas contas estão corretas. Vamos avaliar agora a função que queremos maximizar pesos iniciais aleatórios:

In [139]:
scale<-.1

#inicializando W1
mu1 <- rep(0,(d+1)*k1)
Sigma1 <- scale*diag((d+1)*k1)
W1 <- matrix(mvrnorm(1, mu1, Sigma1), nrow = (d+1), byrow = F) #cada coluna diz respeito a uma variável de entrada

#inicializando w2
mu2 <- rep(0,(k1+1)*k2)
Sigma2 <- scale*diag((k1+1)*k2)
w2 <- mvrnorm(1, mu2, Sigma2)

#Avaliando g
g(y,X,W1,w2,alpha)
-1885,75748556854

Vamos agora aplicar o mini-batch gradient ascent para maximizar nossa função $g$:

In [140]:
lr=0.001
epochs=500
g_hist=c()
batch=250
mom=.1
iter=round(nrow(data)/batch)

for(i in 1:epochs){
    
    #shuffle dataframe
    data <- data[sample(nrow(data)),]
    X <- as.matrix(data[,-NCOL(data)])
    y <- as.matrix(data[,NCOL(data)])
    
    for(j in 1:iter){
        
        #Index
        index <- ((j-1)*batch+1):(min(j*batch,nrow(data)))

        #Update
        w2 <- w2 + lr*grad2(y[index],X[index,],W1,w2,alpha) 
        W1 <- W1 + lr*grad1(y[index],X[index,],W1,w2,alpha)

    }
    
    #Loss
    g_hist <- c(g_hist,g(y,X,W1,w2,alpha))
}

Visualizando o histórico de treino:

In [141]:
plot(g_hist)

Avaliando nossa função novamente, mas agora com os pesos treinado e em nossa base de testes:

In [142]:
#Criando base para testes

X0 <- rbind(mvrnorm(n = n0/3, mu=c(1,2), matrix(c(2,-1,-1,1),2,2)), 
            mvrnorm(n = n0/3, mu=c(-3,-1), matrix(c(2,-1,-1,1),2,2)),
            mvrnorm(n = n0/3, mu=c(-2,2), matrix(c(2,1,1,1),2,2)))
X1 <- mvrnorm(n = n1, mu=c(-0.5,0), matrix(c(1.5,0,0,1),2,2))

y0 <- rep(0,n0)
y1 <- rep(1,n1)

X_test <- rbind(X0,X1)
y_test <- c(y0,y1)
In [143]:
#Avaliando g
g(y_test,X_test,W1,w2,alpha)
-746,861631298124

Observe que a função $g$ nos retorna resultados muitos parecidos tanto para o conjunto de treino quanto para o conjunto de teste, o que nos dá indícios que nossa rede 'overfita' pouco ou não 'overfita'. Vendo as probabilidades preditas pela nossa rede quando fixamos os pesos em $\mathbf{w}_{\text{MAP}}$ (uma solução pontual para o problema):

In [144]:
data <- as.data.frame(cbind(X_test, y_test, h(X_test,W1,w2)))
names(data) <- c('x1','x2','y','prob')

qplot(x1, x2, colour = prob, data = data)

Agora que encontramos $\mathbf{w}_{\text{MAP}}$ nos resta o cálculo de $A=-\frac{\partial ^2 }{\partial \mathbf{w}^2}\text{ln}p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)$. Veja que o que fizemos até o momento é o cálculo de $\frac{\partial }{\partial \mathbf{w}}\text{ln}p(\mathbf{w}_{\text{MAP}}|\mathbf{y},\mathbf{x},\alpha)$, que é o gradiente. Como teremos que realizar o cálculo de $A$ somente uma vez e a nossa rede neural não é muito grande, aproximaremos os valores da matriz numericamente e depois a invertaremos para obter a covariância da posteriori aproximada. Para o cálculo numérico utilizaremos as funções grad1 e grad2 que já foram codificadas - variando elementos de $\mathbf{W}_1$ e de $\mathbf{w}_2$ de forma 'infinitesimal' guardaremos as taxas de variação nos outputs de grad1 e grad2 como as nossas segundas derivadas (veja que a matriz $A$ deve ser simétrica dada a natureza do nosso problema).

In [145]:
reshape_W1_1<-function(W1){matrix(W1)} # (d+1) X k1 >>> (d+1)*k1 X 1
reshape_W1_2<-function(W1){matrix(W1,d+1,k1)} # (d+1)*k1 X 1 >>> (d+1) X k1

grad1_2<-function(y,X,W1,w2,alpha){return(reshape_W1_1(grad1(y,X,reshape_W1_2(W1),w2,alpha)))}
In [146]:
#d=2
In [147]:
#Variando W1

#Bloco 1 (up left)
del=10^(-10)
r_W1=reshape_W1_1(W1)

block1<-c()

for(i in 1:(prod(dim(W1)))){
    u=rep(0,prod(dim(W1)))
    u[i]=del
    block1<-cbind(block1, (grad1_2(y,X,r_W1+u,w2,alpha)-grad1_2(y,X,r_W1-u,w2,alpha))/(2*del))
}
In [148]:
#Variando w2

#Bloco 3 (bottom left)
del=10^(-10)
r_W1=reshape_W1_1(W1)

block2<-c()

for(i in 1:length(w2)){
    u=rep(0,length(w2))
    u[i]=del
    block2<-cbind(block2,(grad1_2(y,X,r_W1,w2+u,alpha)-grad1_2(y,X,r_W1,w2-u,alpha))/(2*del))
}
In [149]:
#Variando w2

#Bloco 3 (bottom left)
del=10^(-10)

block3<-c()

for(i in 1:length(w2)){
    u=rep(0,length(w2))
    u[i]=del
    block3<-cbind(block3, (grad2(y,X,W1,w2+u,alpha)-grad2(y,X,W1,w2-u,alpha))/(2*del))
}

Matriz A:

In [150]:
block2<-0*block2
In [151]:
A <- -cbind(rbind(block1,t(block2)),rbind(block2,block3))

dim(A)
  1. 21
  2. 21

Calculando a covariância invertendo A:

In [152]:
cov<-solve(A)
In [153]:
for(i in 1:NROW(cov)){
    for(j in 1:i){
        m<-mean(cov[i,j],cov[j,i])
        cov[i,j]<-m
        cov[j,i]<-m
    }
}

Checando os autovalores de Cov (todos devem ser não negativos, pois se trata de uma matrix de covariância):

In [154]:
e <- eigen(cov)
e$values
  1. 0,812240923041871
  2. 0,681825439090326
  3. 0,207825188254148
  4. 0,116917959215374
  5. 0,0814987412322791
  6. 0,0533747846189006
  7. 0,039734017060937
  8. 0,0247866570331013
  9. 0,022460075597887
  10. 0,0175363777526934
  11. 0,0174678490747647
  12. 0,014303403411889
  13. 0,00955578554272174
  14. 0,00342579288757021
  15. 0,00318602916705086
  16. 0,00221246166511211
  17. 0,00198440261316859
  18. 0,000815006216912234
  19. 0,000733743720486931
  20. 0,000402119793064704
  21. 0,000219845981560039

Concatenando nossos pesos para obter o $\mathbf{w}_{\text{MAP}}$:

In [155]:
w_map<-rbind(reshape_W1_1(W1),w2)
In [156]:
length(w_map)
dim(cov)
21
  1. 21
  2. 21

Gerando uma instância de $\mathbf{w}$ como exemplo:

In [157]:
mvrnorm(1, w_map, cov)
  1. -0,817125450313554
  2. -0,458454504416359
  3. -1,3631158413735
  4. -0,207571938899375
  5. 1,02729458017256
  6. 0,70910030601371
  7. -0,988119673349491
  8. -0,993432531193449
  9. 0,447196129385517
  10. 0,105317666908501
  11. 0,339215178526227
  12. 1,2399507670077
  13. 1,64086051685046
  14. 0,206580870160829
  15. -0,205207984709569
  16. 1,32551941878877
  17. -1,73325184527579
  18. -1,47600394643825
  19. -1,3518857300108
  20. -0,975530666224972
  21. 1,51678904327348

Assim como já fizemos, amostraremos da distribuição dos pesos (agora da posteriori) e vamos obter 1000 predições para cada indivíduo que dependem de cada um dos valores amostrados para $\mathbf{w}$:

In [158]:
iter <- 1000
simul <- c()

mu1 <- rep(0,(d+1)*k1)
Sigma1 <- alpha*diag((d+1)*k1)

for(i in 1:iter){
    
    #Amostrando w
    w <- mvrnorm(1, w_map, cov)
    W1 <- reshape_W1_2(w[1:((d+1)*k1)])
    w2 <- w[-(1:((d+1)*k1))]
    
    #Rbind
    simul <- cbind(simul, h(X_test,W1,w2))
    }

Agora vamos ver as distirbuições de predições para dois indivíduos, começando pelo indivíduo 3:

In [163]:
hist(simul[3,],main='Histograma da probabilidade de sucesso para o indivíduo 3')

É possível ver que a rede está muito certa que este indivíduo é da classe 0. Olhando agora para o indivíduo 8, podemos ver que a rede está menos certa a respeito de que classe pertence o indivíduo:

In [167]:
hist(simul[8,], main='Histograma da probabilidade de sucesso para o indivíduo 8')

O fato de a rede estar mais ou menos certa de que classes os indivíduos são tem muito a ver com a localização do indivíduo. Veja que o indivíduo 8 está em uma zona de divisão entre as classes, enquando o indivíduo 3 está em uma zona que é predominantemente da classe 0:

In [168]:
X_test[3,]
X_test[8,]
  1. -0,557316263746384
  2. 3,73922577661552
  1. 0,319958837620793
  2. 1,73931818252517
In [162]:
data <- as.data.frame(cbind(X_test, y_test))
names(data) <- c('x1','x2','y')

qplot(x1, x2, colour = y, data = data)