eXclusive OR (XOR) es una operación lógica booleana muy utilizada en criptografía y en la generación de bits de paridad para la comprobación de errores y la tolerancia a fallos. XOR compara dos bits de entrada y genera un bit de salida. La lógica es sencilla. Si los bits son iguales, el resultado es 0. Si los bits son diferentes, el resultado es 1.
El ejemplo que se trabajo en clase se trata de una red neuronal con dos inputs, dos neuronas ocultas y una neurona de salida, con la función Sigmoide como función de activación. Este código fue desarrollado con el objetivo de ejecutar automaticamente n-iteraciones del proceso de retropropagación que busca el entrenamiento del algoritmo optimizando los pesos para que la red neuronal pueda aprender a asignar entradas arbitrarias con su respecctiva salida.
Se utilizaron las librerías ‘igraph’, ‘ggraph’ y ‘ggplot2’ para la graficación de la red neuronal, y la librería Deriv para las derivadas parciales que se requieren para el proceso de retropropagación.
Inicialmente se asignan los pesos iniciales de cada aristas y los sesgos, para el ejemplo se trabajo con los valores iniciales que se pueden observar, y con 0 sesgos:
#Ingrese el valor de cada arista
w1 <<- 0.1
w2 <<- 0.5
w3 <<- -0.7
w4 <<- 0.3
w5 <<- 0.2
w6 <<- 0.4
#Ingrese el valor de los sesgos
b1 <<- 0
b2 <<- 0
b3 <<- 0
k <<- 1
#Función de activación Sigmoide
f_activacion <- function(x) {
1/(1 + exp(-x))
}
#Derivada de la función de activación
df_activacion <- Deriv(f_activacion, "x" )
#Función que ejecuta una época
f_epoca <- function(x1,x2) {
#h1
z1 = w1*x1 + w3*x2 + b1
h1 = f_activacion(z1)
#h2
z2 = w2*x1 + w4*x2 + b2
h2 = f_activacion(z2)
#o1
z3 = w5*h1 + w6*h2 + b3
o1 = f_activacion(z3)
return(list(z1=z1, h1=h1, z2=z2, h2=h2, z3=z3, o1=o1))
}
#Función error total
f_error_total <- function(o_esp, o1) {
sum(0.5 * (o_esp - o1)^2)
}
#Derivada del error total
df_error_total <- Deriv(f_error_total, "o1")
#Función que genera al grafo
f_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","green","green","green")
)
#Creación del grafo
g <- graph_from_data_frame(edges, vertices = nodes)
#Graficación
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"="#F52754","hidden"="#F52754","output"="#F52754","bias"="black")) +
scale_color_manual(values = c(
input = "black", hidden = "black", output = "black", bias = "grey"
)) +
guides(shape = "none", color = "none", fill = "none")+
theme_void()
}
Al momento de ejecutar el código, se asignan los valores de entrada x1 y x2, el valor que se espera obtener al ejecutar la función y el número de épocas que se quieran observar, luego de esto el código ejecuta un ciclo iterativo en el cual se ejecuta cada época. En cada época se genera inicialmente la red neuronal con los pesos actuales, luego de eso se muestra la salida que se obtuvo y el error total, luego de esto internamente se realiza el proceso de retropropagación y se actualizan los pesos de la red neuronal para dar paso a la siguiente epoca. Para este ejemplo se realizara un prueba con 10 épocas:
num1 <- 0 #Digite x1
num2 <- 1 #Digite x2
num3 <- 1 #Digite el valor esperado
num4 <- 10 #Digite el número de épocas que quiere ver
tasa_de_aprendizaje <- 0.25 #Digite la tasa de aprendizaje
#Este ciclo se encarga de todas las operaciones de la retropropagación
while(k <= num4){
#grafico de la epoca
print(f_grafico(w1,w2,w3,w4,w5,w6))
salida <- f_epoca(num1,num2)
print(paste("La salida de la época ", k, " Es la siguiente: ", salida$o1 ))
error <- f_error_total(num3,salida$o1)
print(paste("El error respecto al valor esperado en la época ", k, "fue de: ", error))
#Retropropagacion
#Output layer
dfz3 = df_error_total(num3, salida$o1)*df_activacion(salida$z3)
dfw5 = dfz3*salida$h1
dfw6 = dfz3*salida$h2
actualizacion5 = w5 - tasa_de_aprendizaje*dfw5
actualizacion6 = w6 - tasa_de_aprendizaje*dfw6
#Hidden layer
dfz1 = dfz3*w5*df_activacion(salida$z1)
dfz2 = dfz3*w6*df_activacion(salida$z2)
#neurona h1
dfw1 = dfz1*num1
dfw3 = dfz1*num2
actualizacion1 = w1 - tasa_de_aprendizaje*dfw1
actualizacion3 = w3 - tasa_de_aprendizaje*dfw3
#neurona h2
dfw2 = dfz2*num1
dfw4 = dfz2*num2
actualizacion2 = w2 - tasa_de_aprendizaje*dfw2
actualizacion4 = w4 -tasa_de_aprendizaje*dfw4
#reemplazamos los w
paste("los pesos actualizados son los siguientes: ")
w1 = actualizacion1
w2 = actualizacion2
w3 = actualizacion3
w4 = actualizacion4
w5 = actualizacion5
w6 = actualizacion6
k <- k+1
}
## [1] "La salida de la época 1 Es la siguiente: 0.573498503710098"
## [1] "El error respecto al valor esperado en la época 1 fue de: 0.0909517631687627"
## [1] "La salida de la época 2 Es la siguiente: 0.576379711212853"
## [1] "El error respecto al valor esperado en la época 2 fue de: 0.0897270745360528"
## [1] "La salida de la época 3 Es la siguiente: 0.579242611989336"
## [1] "El error respecto al valor esperado en la época 3 fue de: 0.0885183897827782"
## [1] "La salida de la época 4 Es la siguiente: 0.582087260829031"
## [1] "El error respecto al valor esperado en la época 4 fue de: 0.0873255287806913"
## [1] "La salida de la época 5 Es la siguiente: 0.584913705115242"
## [1] "El error respecto al valor esperado en la época 5 fue de: 0.0861483161005782"
## [1] "La salida de la época 6 Es la siguiente: 0.587721985240695"
## [1] "El error respecto al valor esperado en la época 6 fue de: 0.084986580726937"
## [1] "La salida de la época 7 Es la siguiente: 0.590512135029322"
## [1] "El error respecto al valor esperado en la época 7 fue de: 0.0838401557791221"
## [1] "La salida de la época 8 Es la siguiente: 0.593284182162425"
## [1] "El error respecto al valor esperado en la época 8 fue de: 0.0827088782396437"
## [1] "La salida de la época 9 Es la siguiente: 0.596038148607473"
## [1] "El error respecto al valor esperado en la época 9 fue de: 0.081592588690239"
## [1] "La salida de la época 10 Es la siguiente: 0.598774051047897"
## [1] "El error respecto al valor esperado en la época 10 fue de: 0.0804911310562579"
Como se puede observar, conforme se va ejecutando cada época, el valor de salida se va acercando más y más al valor esperado (que en este caso es 1), y el error total va disminuyendo, lo cual permite identificar la efectividad del algoritmo de aprendizaje.