Gradiente Descendente

Para estimar parâmetros de uma RNA com um único neurônio, função de ativação logistica e função de custo SSE

Author

Jessica Kubrusly

Gradiente Descendente

Suponha conhecidos a matriz \(X\) de dimensão \(n \times m\), com os valores observados para as covariáveis, e o vetor \(Y\) com os valores observados para a variável alvo. O Método do Gradiente Descendentes busca minimizar uma função de custo \(J\).

Para o caso de um problema de regressão, uma função de custo adequada é a SSE. Para um modelod e regressão de RNA essa função depende da função de ativação \(g\).

\[ J(\mathbf{w},\Theta)=\sum_{i=1}^n \left( y_i - g({w_1x_{ì,1}+w_2x_{i,2} + \ldots + w_m x_{i,m}+\Theta }) \right)^2 \] sendo \(g\) a função de ativação.

Para realizar o método iterativo precisamos do gradiente de \(J\). Vejamos como é o vetor gradiente para \(J\) definida acima. \[ \nabla J(\mathbf{w},\Theta)= \left( \frac{\partial J}{\partial w_1}(\mathbf{w},\Theta), \frac{\partial J}{\partial w_2}(\mathbf{w},\Theta), \ldots, \frac{\partial J}{\partial w_m}(\mathbf{w},\Theta), \frac{\partial J}{\partial \Theta}(\mathbf{w},\Theta) \right) \]

\[ \frac{\partial J}{\partial w_j}(\mathbf{w},\Theta) = \sum_{i=1}^n -2\left( y_i - g({w_1x_{ì,1}+w_2x_{i,2} + \ldots + w_m x_{i,m}+\Theta}) \right) g'({w_1x_{ì,1}+w_2x_{i,2} + \ldots + w_m x_{i,m}+\Theta}) x_{i,j} \]

\[ \frac{\partial J}{\partial \Theta}(\mathbf{w},\Theta) = \sum_{i=1}^n -2\left( y_i - g({w_1x_{ì,1}+w_2x_{i,2} + \ldots + w_m x_{i,m}+\Theta}) \right) g'({w_1x_{ì,1}+w_2x_{i,2} + \ldots + w_m x_{i,m}+\Theta}) \]

Então, para calcular o gradiente de \(J\) precisamos de \(g\) e de \(g'\).

Função de Ativação Logística

Se a função de ativação for a logística, temos:

\[ g(x) = \dfrac{1}{1+e^{-x}} \qquad \text{ e } \qquad g'(x) = \dfrac{e^{-x}}{(1+e^{-x})^2} \]

Vamos então criar uma função no R que recebe como entrada um número real \(x\) e retorna o valor de \(g(x)\) dada pela função de ativação:

g = function(x){
  1/(1+exp(-x))
}

e também a função \(g'\) que recebe como entrada um número real \(x\) e retorna o valor de \(g'(x)\):

dg = function(x){
  exp(-x)/(1+exp(-x))^2
}

Função de Custo SSE

A função de custo \(J\) depende do problema que queremos resolver. Para problemas de regressão podemos usar o SSE. Vamos implementar a \(J\) que recebe como entrada os valores dos parâmetros \(\mathbf{w}\) e \(\Theta\) e retorna \(J(\mathbf{w},\Theta)\). É ela que queremos minimizar. No R ela pode ser definida assim:

J = function(theta,w){
  soma = 0
  n = nrow(X)
  for(i in 1:n){
    linha_x = X[i,]
    yi = Y[i]
    soma = soma + (yi - g(sum(w*linha_x) + theta))^2 
  }
  return(soma)
}

Aqui vamos supor que a matriz de dados \(X\) e o vetor \(Y\) estão definidos globalmente, assim como os valores de \(m\) e \(n\). Isto é, temos acesso a eles dentro da função.

Para realizar o método iterativo do Gradiente Descendet precisamos não só de uma função que fornece o valor de \(J\) como também uma que fornece o valor de \(\nabla J\).

gradiente_J = function(theta,w){
  
  n = nrow(X)
  m = ncol(X)
  
  grad = NULL
  
  #derivada em relacao a theta
  soma = 0
  for(i in 1:n){
      linha_x = X[i,]
      yi = Y[i]
      soma = soma - 2*(yi - g(sum(w*linha_x) + theta))*dg(sum(w*linha_x) + theta)
  }
  grad[1] = soma
  
  #derivadas em relacao a wj
  for(j in 1:m){
    soma = 0
    for(i in 1:n){
      linha_x = X[i,]
      yi = Y[i]
      soma = soma - 2*(yi - g(sum(w*linha_x) + theta))*dg(sum(w*linha_x) + theta)*X[i,j]
    }
    grad[j+1] = soma
  }
  
  return(grad)
}

Método Iterativo

O métodeo iterativo do gradiente descendente para encontrar o ponto de mínimo da função de custo \(J\) é definido por:

  • Passo 0: Atribua valores para o hiperparâmetro \(\delta\), \(\varepsilon\) e \(K\).
  • Passo 1: Escolha um ponto inicial qualquer \((\Theta_0,\mathbf{w}_0)\) e faça \(i=0\).
  • Passo 2: Calcule \(\nabla J(\Theta_i,\mathbf{w}_i)\).
  • Passo 3: Atualize o ponto \((\Theta_{i+1},\mathbf{w}_{i+1}) = (\Theta_i,\mathbf{w}_i) - \delta \nabla J(\Theta_i,\mathbf{w}_i)\).
  • Passo 4: Se os critérios de parada não foram satisfeitos, faça \(i = i + 1\) e volte para o Passo 2.

Vamos implementar esse passo-a-passo com uma função recursiva.

gradiente_descendente = function(theta0 = rnorm(1), w0 = rnorm(ncol(X)), k=1, d=0.01, e=0.01, K=1000){
  
  gradJ = gradiente_J(theta0,w0)
  novo = c(theta0,w0) - d*gradJ
  theta1 = novo[1]
  w1 = novo[-1]
  
  if(sqrt(sum(gradJ^2))<e & 
     sqrt(sum((novo - c(theta0,w0))^2))<e & 
     sqrt(sum((J(theta0,w0) - J(theta1,w1))^2))<e){
    print(paste("Convergiu em k =",k))
    return(novo)
  } 
  
  if(k > K){
    print(paste("Não convergiu para k =",k))
    return(novo)
  }
  
  return(gradiente_descendente(theta0 = theta1, w0 = w1, k = k+1,d,e,K))
}

A hora da verdade!!!

Primeiro, carregar os pacotes necessários.

library(tidyverse)
library(neuralnet)

Depois, vamos carregar a matriz de treino com os valores de \(X\) e \(Y\). Aqui vamos uasar já a matriz pré-processada, isto é, não há dados faltantes e as variáveis já foram padronizadas.

base = readRDS("base_treino_final_log.RDS")
colnames(base)
 [1] "age"             "sexmale"         "bmi"             "children"       
 [5] "smokeryes"       "regionnorthwest" "regionsoutheast" "regionsouthwest"
 [9] "charges"         "marriedyes"     
dim(base)
[1] 555  10
Y <<- base[,"charges"]
X <<- base[,c("age","sexmale","bmi","children","smokeryes","regionnorthwest","regionsoutheast","regionsouthwest","marriedyes")]

O comando <<- atribui um valor global ao objeto.

Agora vamos chamar a função que realiza o método iterativo.

pesos_gd = gradiente_descendente()
[1] "Convergiu em k = 321"
theta_gd = pesos_gd[1]
w_gd = pesos_gd[-1]

Vamos agora encontrar os pesos a partir do pacote neuralnet para comparação.

modelo_completo_log = neuralnet(
  formula = charges ~ age + sexmale + bmi + children + smokeryes + regionnorthwest + regionsoutheast +  regionsouthwest + marriedyes, 
  data = base, 
  hidden = 0,
  linear.output = FALSE)

pesos_nn = modelo_completo_log$weights[[1]][[1]][,1]
theta_nn = pesos_nn[1]
w_nn = pesos_nn[-1]

Será que chegamos em valores parecido?

J(theta_gd,w_gd);J(theta_nn,w_nn)
       1 
4.996842 
       1 
4.996877 
theta_gd;theta_nn
[1] -1.997723
[1] -1.995632
w_gd;w_nn
[1]  0.36262654 -0.02207059  0.33390763  0.05798083  2.08891811  0.01214019
[7] -0.03855849 -0.05687830 -0.01681777
[1]  0.36261431 -0.02376300  0.33382611  0.05803448  2.08913956  0.01161832
[7] -0.03887967 -0.05610192 -0.01871452