El modelo que se empleó en la clase es una red neuronal que tiene dos entradas, dos neuronas ocultas y una neurona de salida. La función de activación utilizada es la sigmoide. La finalidad de este código es llevar a cabo n iteraciones del proceso de retropropagación, que tiene como objetivo el entrenamiento del algoritmo mediante la optimización de los pesos. Así, la red neuronal será capaz de aprender a asociar entradas arbitrarias con su salida correspondiente.

#Pesos de las aristas
w1 <- 0.1
w2 <- 0.5
w3 <- -0.7
w4 <- 0.3
w5 <- 0.2
w6 <- 0.4

#Valor de los sesgos  
b1 <- 0
b2 <- 0
b3 <- 0

#Función de activación Sigmoide
funcion_activacion <- function(x) {
  1/(1 + exp(-x))
} 

#Derivada de la función de activación
deriv_activacion <- Deriv(funcion_activacion, "x" ) 

#Función que ejecuta una época
funcion_epoca <- function(x1,x2) {

  z1 = w1*x1 + w3*x2 + b1
  h1 = funcion_activacion(z1)

  z2 = w2*x1 + w4*x2 + b2
  h2 = funcion_activacion(z2)

  z3 = w5*h1 + w6*h2 + b3
  o1 = funcion_activacion(z3)
  
  return(list(z1=z1, h1=h1, z2=z2, h2=h2, z3=z3, o1=o1))
  
} 

#Función error total
funcion_error_total <- function(o_esp, o1) {
  0.5 * (o_esp-o1)^2
} 

#Derivada del error total 
deriv_error_total <- Deriv(funcion_error_total, "o1") 
#Función que genera al grafo
funcion_grafico <- function(x1,x2,x3,x4,x5,x6){
  #Nodos
  nodes <- data.frame(
    name = c("X1","X2","H1","H2","O1","B1","B2"),
    label = c("X1\n0","X2\n1","H1","H2","O1\n1","B1","B2"),
    type = c("input","input","hidden","hidden","output","bias","bias"),
    x = c(0,0,3.5,3.5,7,2,5),
    y = c(3,0,3,0,1.5,4,4)
  )
  
  #Aristas
  edges <- data.frame(
    from = c("X1","X1","X2","X2","H1","H2","B1","B1","B2"),
    to   = c("H1","H2","H1","H2","O1","O1","H1","H2","O1"),
    label= c(paste("W1 = ",x1),paste("W2 = ",x2),paste("W3 = ",x3)
             ,paste("W4 = ",x4),paste("W5 = ",x5),paste("W6 = ",x6),
             "B1 = 0","B2 = 0","B3 = 0"),
    color= c("black","black","black","black","black","black","#1874CD","#1874CD","#1874CD")
  )
  
  #Creación del grafo
  g <- graph_from_data_frame(edges, vertices = nodes)
  ggraph(g, layout = "manual", x = nodes$x, y = nodes$y) +
    
    #Aristas
    geom_edge_link(aes(label = label, color = I(color)),
                   angle_calc = 'along',
                   label_dodge = unit(2.5, 'mm'),
                   label_size = 3,
                   label_pos = 0.3,
                   arrow = arrow(length = unit(3, 'mm'), type = "closed"),
                   end_cap = circle(16, 'pt'))+
    
    #Nodos
    geom_node_point(aes(shape = type, color = type, fill = type), size = 15, stroke = 1) +
    
    geom_node_text(aes(label = label, x = x, y = y), color="black", size=4, fontface="bold") +
    
    scale_shape_manual(values = c(
      input = 21, hidden = 21, output = 21, bias = 17  #21 es circulo, 17 es triangulo
    )) +
    
    scale_fill_manual(values=c("input"="#00CDCD","hidden"="#00CDCD","output"="#00CDCD","bias"="black")) +
    
    scale_color_manual(values = c(
      input = "black", hidden = "black", output = "black", bias = "grey"
    )) +
    
    guides(shape = "none", color = "none", fill = "none")+
    
    theme_void()
} 

Cuando se lleva a cabo la ejecución del código, primero se asignan los valores de entrada x1 y x2, el número de épocas a observar y el valor que debe lograrse al ejecutar la función. Después de ello, un ciclo iterativo ejecuta cada época. En cada era, la red neuronal se establece primero con los pesos actuales; después, se presenta el resultado y el error total. Después de eso, se lleva a cabo internamente el proceso de retropropagación y los pesos de la red neuronal se actualizan antes de continuar con la siguiente época. Se llevará a cabo una prueba con 10 épocas para este caso.

x1 <- 0 
x2 <- 1 
o1 <- 1 
num_epocas <- 5 
tasa_de_aprendizaje <- 0.25 


#En este ciclo realizamos las operaciones de retropropagación
k <- 1
while(k <= num_epocas){
  
  print(funcion_grafico(w1,w2,w3,w4,w5,w6))  
  
  #El error y el valor de o1 (la salida)
  ep<-funcion_epoca(x1,x2)
  print(paste("El valor de o1 o salida de la época",k,"es",ep$o1 ))
  print(paste("El error total para la red neuronal respecto al valor esperado en la época",k, "es de",funcion_error_total(o1,ep$o1)))
  
  
  #Capa de salida
  deriv_z3 = deriv_error_total(o1, ep$o1)*deriv_activacion(ep$z3)
  
  deriv_w5 = deriv_z3*ep$h1
  deriv_w6 = deriv_z3*ep$h2
  
  act5 = w5 - tasa_de_aprendizaje*deriv_w5
  act6 = w6 - tasa_de_aprendizaje*deriv_w6 
  
  #Capa oculta
  deriv_z1 = deriv_z3*w5*deriv_activacion(ep$z1)
  deriv_z2 = deriv_z3*w6*deriv_activacion(ep$z2)
  
  #neurona h1
  deriv_w1 = deriv_z1*x1
  deriv_w3 = deriv_z1*x2
  act1 = w1 - tasa_de_aprendizaje*deriv_w1
  act3 = w3 - tasa_de_aprendizaje*deriv_w3
  
  #neurona h2
  deriv_w2 = deriv_z2*x1
  deriv_w4 = deriv_z2*x2
  
  act2 = w2 - tasa_de_aprendizaje*deriv_w2
  act4 = w4 -tasa_de_aprendizaje*deriv_w4
  
  #Se reemplazan los pesos
  w1 = act1
  w2 = act2
  w3 = act3
  w4 = act4
  w5 = act5
  w6 = act6
  
  k <- k+1
}

## [1] "El valor de o1 o salida de la época 1 es 0.573498503710098"
## [1] "El error total para la red neuronal respecto al valor esperado en la época 1 es de 0.0909517631687627"

## [1] "El valor de o1 o salida de la época 2 es 0.576379711212853"
## [1] "El error total para la red neuronal respecto al valor esperado en la época 2 es de 0.0897270745360528"

## [1] "El valor de o1 o salida de la época 3 es 0.579242611989336"
## [1] "El error total para la red neuronal respecto al valor esperado en la época 3 es de 0.0885183897827782"

## [1] "El valor de o1 o salida de la época 4 es 0.582087260829031"
## [1] "El error total para la red neuronal respecto al valor esperado en la época 4 es de 0.0873255287806913"

## [1] "El valor de o1 o salida de la época 5 es 0.584913705115242"
## [1] "El error total para la red neuronal respecto al valor esperado en la época 5 es de 0.0861483161005782"

Como se puede ver, a medida que cada época se lleva a cabo, el valor de salida se aproxima cada vez más al valor esperado (en este caso 1), y el error total disminuye, acercándose a 0. Esto permite determinar la eficacia del algoritmo de aprendizaje.