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.