logo

Dot CSV

Inteligencia Artificial, Tecnología, Ciencia y Futuro! Bienvenidos a la 4ª Revolución Industrial 🚀 Inteligencia Artificial, Tecnología, Ciencia y Futuro! Bienvenidos a la 4ª Revolución Industrial 🚀

Transcribed podcasts: 213
Time transcribed: 5d 22h 50m 3s

This graph shows how many times the word ______ has been mentioned throughout the history of the program.

¡Hola chicos y chicas! Bienvenidos a.csv, tu canal sobre inteligencia artificial, que hoy por fin va a ser eso que tanto me habéis pedido.
Hoy vamos a implementar una red neuronal.
Efectivamente, por fin estamos llegando a este punto en el que tras haber visto toda la teoría, toda la intuición sobre qué es una red neuronal, sobre cómo funciona,
pues nos vamos a poner un poquito manos a la obra, vamos a ver cómo se implementa en código todo lo que hemos ido viendo durante todos estos meses en la serie de
Entendiendo Inteligencia Artificial sobre todos los capítulos de redes neuronales, los cinco capítulos.
Una cosa que quiero decir, por cierto, se ha acabado la serie de redes neuronales, esa serie de cinco capítulos, que han sido cinco partes de un mismo tema,
pero eso no significa que acabe el canal de hablar sobre redes neuronales, de hecho es todo lo contrario.
Doy por terminada lo que sería el conocimiento básico de lo que es una red neuronal y a partir de ahora vamos a empezar a ver cosas muy chulas.
Vamos a ver diferentes tipos de redes neuronales, como las convolucionales, como las recurrentes, diferentes tipos de arquitectura de deep learning.
Vamos a ver cómo se entrena realmente y cuáles son los trucos.
Vamos a implementar una red neuronal y también otra cosa que quiero decir es que aunque hoy vamos a implementar una red neuronal va a haber un apartado
que se añadirá a este vídeo, posiblemente esta semana también, un ademdum por así llamarlo, no sé si se dice así.
Pero bueno, es un vídeo en el que vamos a realmente ver cómo se implementa una red neuronal en el mundo real, por así decirlo.
Porque realmente lo que vamos a hacer hoy es programar desde cero una red neuronal, es decir, vamos a coger el código, vamos a implementar todas las fórmulas que hemos ido viendo.
Pero la realidad es que un ingeniero de machine learning o cualquier persona que se vaya a utilizar una red neuronal que necesita utilizarlo para su código,
pues lo más normal es que utilice una librería de las que ya existen como puede ser SKlearn o Tensorflow o PyTorch o alguna de estas variedades.
Entonces en ese vídeo, que no va a ser el de hoy, pues explicaré qué diferencias te suponen, les mostraré cómo se hace que es bastante sencillo, entre comillas,
cómo el código se reduce simplemente a un par de líneas y bueno, simplemente un poco un acercamiento a lo que posiblemente sigamos haciendo en el canal
porque ya daremos el salto de implementar desde cero a estar utilizando estas librerías más avanzadas.
Bueno, quería decir solamente eso, a lo mejor algunos se extraña de que estén habiendo tantos vídeos por fin en el canal después del ritmo lento que ha habido,
que estaba haciendo por costumbre, pero si esto es posible, y voy a aprovechar este momento ahora, si esto está siendo posible es porque me he tomado la libertad de estar estos meses,
esto posiblemente hasta diciembre y ya veré si continúo así, dedicándome full time al canal, o sea no full time porque en realidad tengo otros proyectos que son los que me van a pagar las facturas,
pero sí para mí el canal es una cosa en la que realmente creo, es un proyecto personal que me llena bastante y por eso me estoy dedicando a estar metido completamente en este tema
y a estar sacando vídeos y avanzar un poco con todas las ideas que tengo en la mente.
Esto es posible en parte por el dinero que me llega de Patreon, por el dinero que me llega de AdSense, de la publicidad que sale en los vídeos,
y simplemente agradecer a los Patreons que están soportando este canal que me da esta libertad para decidir en un momento puntual hacer esto que estoy haciendo ahora.
Si quieres apoyar el canal pues ya lo digo siempre, puedes hacerte Patreon, puedes donar un euro si quieres un dólar, el equivalente a un café al mes o dos cafés o tres cafés o una cafetera entera,
lo que tú quieras, pero bueno, eso ya ven que repercute en la posibilidad de que yo pueda tener mayor flexibilidad.
Dicho esto, vamos a empezar a programar la red neuronal, la idea es que según terminemos este vídeo tú hayas programado tu red neuronal por ti mismo entera y que sea funcional.
Para eso, como quiero que todo el mundo tenga la posibilidad de entrenar a la red neuronal y que no haya problemas de hoy yo es que no tengo instalado esto en mi ordenador, yo no tengo tal cosa,
hoy vamos a utilizar, no sé si lo hemos utilizado antes, pero bueno, vamos a utilizar una herramienta que se llama Google Collab.
Google Collab básicamente es una forma de utilizar los cuadernos Jupyter que hemos ido utilizando en los otros tutoriales online,
básicamente significa que no tienes que instalarte en tu ordenador ni Jupyter Notebooks ni Python ni librerías ni nada,
o sea directamente te metes en la página de Google Collaboratory y ahí pues ya tienes acceso a crearte tu propio cuaderno de Python 3 como ven aquí,
y cuando te creas tu propio cuaderno pues tú puedes empezar a programar tu código, puedes empezar a utilizar librerías que ya están pre-instaladas
y lo mejor de todo es que también lo puedes ejecutar y en este caso no se está ejecutando en tu ordenador,
sino que se está ejecutando online en hardware que Google pone a tu disposición de manera gratuita,
y esto está guay porque el hardware que te proporcionan es tanto una CPU que en ese caso sí está bastante bien para el tema de Machine Learning
porque puede agilizar todo el procesamiento de tema de gráficos y tensores y productos matriciales y también TPUs,
que esto es algo nuevo que han incorporado y que está también bastante bien,
a nivel de experimentos pues tú puedes empezar a trastear, a hacer por ejemplo lo que vamos a hacer hoy,
una red neuronal y a coste cero y directamente con la comodidad de que está todo online lo puedes hacer con cualquier ordenador,
moverte a otro ordenador y sigues teniendo tu trabajo.
Entonces, esa es la herramienta que vamos a utilizar hoy y es la que están viendo ahora mismo en pantalla.
¿Lo tienes abierto ya? Perfecto, comenzamos.
Vamos a empezar como empezamos siempre importando nuestras librerías de cabecera, importamos Numpy,
librería para el procesamiento numérico, importamos SciPy que añade y expande las funcionalidades de Numpy con más cositas,
importamos la que nos va a servir para gráficos que es Matplotlib by Plot y le llamamos PLT
y también vamos a importar una funcionalidad que nos va a ser útil que viene de la mano de la librería SKlearn
que como muchos sabrán tiene bastantes funcionalidades interesantes para el Machine Learning.
En este caso la utilidad que vamos a coger es esta de aquí que se llama Make Circle, hacer círculos
y básicamente lo vamos a utilizar porque el problema que vamos a resolver en este tutorial es el que vimos
en el segundo capítulo de la serie de redes neuronales que si no se acuerdan era el tema este de Juego de Tronos
que tiene a los héroes rodeados por los enemigos, entonces como yo te mostraba una nube de puntos que era circular
y otra que otro círculo exterior, entonces lo que estamos buscando era resolver un problema de clasificación
donde separábamos a un grupo y al otro.
Pues este es el problema que vamos a resolver hoy y para eso vamos a utilizar esta funcionalidad
porque tenemos que crear nuestro propio dataset que tenga ese tipo de datos
y eso es lo que nos da esta funcionalidad aquí.
Vamos a ejecutar esta celda para importar las librerías si está todo correcto.
Bueno yo la acabo de dar a Ctrl-Enter y es con Ctrl-Intro y es con lo que he ejecutado la celda.
Me faltaba la S aquí. Una vez tenemos esto importado vamos a crear el dataset.
Entonces para crear el dataset vamos a hacer uso de la función Make Circles
y si por ejemplo no te acuerdas de cómo se utiliza puedes poner esto, el nombre de la función y la interrogación
que ejecutas la celda y te sale la documentación.
Aquí te dice Make Circles, tiene parámetros en Samples, Shuffle, Noise, Random Estate, Factor.
Antes de esto vamos a definir dos variables que siempre son útiles que es n y p.
N viene a referirse al número de registros que tenemos en nuestro dato, en este caso 500.
Y p viene a referirse a cuántas características tenemos sobre cada uno de los registros de nuestro datos.
En este caso dos. Es decir, si por ejemplo estuviéramos tratando con un dataset de personas
pues estaríamos hablando de que tenemos 500 personas y estamos de cada persona tratando dos características diferentes.
Que sería por ejemplo el nombre y la altura. Bueno el nombre no tiene mucho sentido, la edad y la altura.
Esas serían nuestras dos características. En este caso estamos utilizando p igual a 2.
Podríamos normalmente en un problema de Machine Learning pues tu p sería bastante más grande y tu n también sería mucho más grande.
Pero en este caso solamente vamos a utilizar dos porque esto va a simplificar las visualizaciones.
Porque al fin y al cabo cuando queramos visualizar los puntos y solamente tenemos dos características
lo podemos visualizar en un eje de coordenada bidimensional.
Y ese va a ser el caso. Entonces vamos a utilizar esta función de aquí makeCircles para que nos genere el dataset.
Tanto la x como la y. El input y el output.
Entonces a makeCircles nos decía que teníamos primero que pasarlo a un parámetro que se llamaba makeNSamples
y en nuestro caso nSamples es igual a n.
Y luego yo sé que nos interesa utilizar este parámetro al 0,5 si no recuerdo mal.
Lo que vamos a hacer es visualizar directamente el contenido de esta variable para que lo vean bien.
Estamos usando la librería Matplotlib para visualizarlo.
Visualizamos todos los puntos y en el eje x la coordenada número 1, la columna número 1.
Y en el eje y todos los puntos y la coordenada número 2 que sería el índice 1.
Como ven aquí tendríamos nuestros círculos de datos.
Y el parámetro de este factor que yo he puesto 0,5 básicamente nos habla de la distancia entre los dos círculos.
Si lo pones 0,9 estaría muy cerca. 0,5 a la mitad.
Yo creo que así está bien y también para poner un poco más de dificultades en el problema vamos a ponerle un poco de ruido.
Vamos a probar con 0,4. Esto es demasiado. 0,2, demasiado. 0,05. Vale, ahí estaría.
Vamos a añadir un poco de variabilidad entre todos nuestros puntos para que sea un poco más complicado y se asemeje un poco más a la realidad.
Dentro de lo que cabe en este problema.
De hecho vamos a utilizar en el eje x, o sea no, en la variable x tenemos el dataset que estamos utilizando como parámetro de entrada.
Nuestros puntos, los que estamos visualizando ahí.
Eje y, si por ejemplo lo imprimimos, puede ver que es un vector binario.
Tenemos ceros y unos que se corresponden a las dos clases que pueden ser cada uno de los puntos.
Si pertenece a un círculo o pertenece al otro.
Entonces podemos utilizar aquí para hacer este plot más bonito e interesante.
Podemos utilizar la información del vector y y decir, mira, cuando i sea igual a cero.
Y cuando i sea igual a cero de nuevo.
Con lo cual aquí ahora solamente estaríamos mostrando uno de los círculos.
Y esto le podemos asignar un color. Por ejemplo sky blue.
En beta azul usan sky blue. Es más bonito.
Y podemos hacer lo mismo con el otro círculo.
Vamos a ponerle color salmón, que es la versión bonita del rojo.
Y vamos a decir que solamente nos muestra los puntos cuyo índice en el vector y sea igual a uno.
Y aquí tendríamos, a ver el último truquito para que se vea muy bien.
Pelete axis, le decimos a la librería Matplotlib que nos visualice los ejes con la misma proporción.
Muy bien, aquí tendríamos un ejemplo de cómo se visualiza una solución de nuestro dato.
Tendríamos que separar, tenemos que conseguir que nuestra red neuronal separe en dos clases diferentes esta nube de puntos de aquí.
Vamos a por ello.
Una vez tenemos creado esto podemos empezar a programar nuestra red neuronal.
Y lo primero que vamos a hacer es pararnos a pensar.
Porque como sabrán, hemos dicho mucho en la serie que la red neuronal, su unidad de procesamiento es la neurona, efectivamente.
Y que podemos juntar muchas neuronas en diferentes capas para conseguir esta codificación de información más compleja.
Y que pueda procesar información más compleja a la red neuronal.
Y es cierto.
Pero otra forma de verlo, si se fijan, es que casi todas las fórmulas, por no decir todas las que hemos utilizado dentro de nuestra red neuronal.
Al final han resultado ser posibles de vectorizar.
Que es uno de los motivos por los cuales conseguimos un mejor rendimiento a la hora de procesar información con las redes neuronales.
Porque están vectorizadas y eso nos permite tratar las fórmulas de forma vectorizada.
Eso quiere decir que dentro de una capa, nosotros estamos realizando las mismas operaciones para todas las neuronas.
Dentro de una misma capa. Porque tienen la misma función de activación, porque aplicamos las mismas fórmulas de backpropagation.
Por todos esos motivos.
Eso quiere decir que en realidad si nosotros podemos considerar la red neuronal como si fuera la unidad de procesamiento a las neuronas.
Otra forma de verlo también podría ser pensar que cada capa es un módulo.
Dentro de una misma capa van a realizarse las mismas operaciones.
Eso hasta voy a pensarlo así porque te da una nueva forma de ver todo lo que son las redes neuronales y todo el campo del deep learning.
Donde el módulo principal es la capa.
Y cada capa se diferenciará si realiza un tipo diferente de operación.
En una convolucional puedes tener capas que sean convoluciones o max pooling o lo que sea.
O con catenación simplemente.
Entonces esto es interesante porque nos va a permitir tener el código mejor ordenado.
Por todos esos motivos vamos a tomar esa perspectiva y vamos a definir primero en nuestra red neuronal una clase, un tipo de objeto que se refiera a una capa.
Entonces vamos a crear una clase en Python que se llame NeuralLayer.
Con esto lo que decimos ahora creamos el inicializador de la clase.
Y aquí es donde tenemos que decir que parámetros le vamos a pasar a nuestra clase a la hora de crearla.
En nuestro caso la clase NeuralLayer no va a ejecutar nada de lógica, no va a tener ninguna función.
Podríamos crear funciones, podríamos crear una función que haga el procesamiento hacia adelante y otro hacia detrás.
Pero eso por tenerlo ordenado y no liarnos mucho prefiero separarlo.
Y esto solo lo vamos a utilizar como una estructura de datos.
Vamos a tener solamente los parámetros de la capa que si recuerdan son W y B.
Entonces, ¿qué parámetros hacen falta inicializar en nuestra capa?
Tenemos que especificar cuántos son el número de conexiones que entran a nuestra capa de la capa anterior.
Tenemos una capa, la capa siguiente.
Tenemos que especificar cuántas conexiones van a haber.
Vamos a crear un parámetro que se llame número de conexiones.
Y también tenemos que definir cuántas neuronas hay en nuestra capa.
Vamos a definir entonces un parámetro que se llame nneur, número de neuronas.
Lo último que tenemos que definir es un parámetro que sea la función de activación que se está ejecutando dentro de las neuronas de esta capa.
Vamos a llamarle ActivationFunction en inglés.
Entonces, esta sería la función que va a inicializar a nuestra clase en NeuralLayer.
Y aquí es donde vamos a tener que definir, por ejemplo, que la función de activación de la capa va a ser igual al parámetro que hemos pasado.
El parámetro este de aquí arriba que es ActivationFunction.
Y luego, esta información de aquí la podemos utilizar para inicializar los parámetros de la capa.
Pues en este caso podemos crear un vector que se refiera al parámetro de vallas que tendremos tantos como neuronas tenga nuestra capa.
Y aquí en este caso los parámetros de la red neuronal se pueden inicializar de diferentes maneras.
Y en este caso lo vamos a inicializar simplemente de forma aleatoria.
Vamos a usar la función Random de la librería Numpy.
Y vamos a especificar que para el vallas queremos que sea un vector columna donde esto de aquí es 1.
Y la cantidad de parámetros que vamos a tener, como hemos dicho, es el número de neuronas.
En este caso la función Random nos va a devolver un valor entre 0 y 1 aleatorio.
Pero en este caso nos interesa que la inicialización aleatoria de la red neuronal sea en torno a la media 0.
Que esté normalizada y estandarizada.
Entonces para eso, como estamos en 0 y 1 y lo queremos de menos 1 a 1, lo que vamos a hacer es multiplicar por 2.
Así iría de 0 a 2 y le vamos a restar 1. Y así iría de menos 1 a 1.
Esto mismo vamos a hacer para el vector W.
Entonces vamos a coger aquí, cambiamos esto por esto.
Y en este caso el vector W no es un vector como estoy diciendo, sino que es una matriz cuyos valores serán el número de conexiones y el número de neuronas.
Recordemos que entre dos capas tenemos tantas conexiones como número de neuronas hubiera en la capa anterior y número de neuronas tengamos en la capa actual.
Con esto ya habríamos definido, vamos a ejecutar esta celda para ver si está todo correcto, parece que sí.
Pues con esto ya habríamos definido una clase con la cual podemos crear capas para nuestra red neuronal.
Repito, aquí no hemos creado ninguna lógica, no hemos implementado ninguna lógica de las que hemos visto y de las que vamos a hacer ahora.
Sino simplemente es una estructura de datos que contiene la información, los parámetros de nuestra red neuronal.
Otra cosa que vamos a necesitar e implementar, como bien sabemos y ya nos hemos referido ahí aquí, son las funciones de activación.
Si recuerdan, la función de activación, de hecho vamos a poner esto un poco ordenado, vamos a poner primero aquí, clase de la capa de la red.
Ahora vamos a crear las funciones de activación.
Si recuerdan, las funciones de activación son la función por la cual se pasa la suma ponderada que se realiza en la neurona
y que va a introducir dentro de nuestra red neuronal no linealidades, que básicamente es lo que te va a permitir combinar muchas neuronas.
Porque si no, ya recuerdan que esto se convertía solamente en una única función lineal y se nos va todo el invento para abajo.
Entonces, si recuerdan, teníamos diferentes tipos de funciones de activación, teníamos la sigmoide, teníamos la relu, teníamos la tangencial hiperbólica
y en este caso vamos a implementar estas funciones.
Vamos a por ejemplo implementar la sigmoide, definimos una variable que le llamamos sigm
y vamos a implementarla a modo de función anónima en python.
Eso se hace con la palabra clave reservada lambda x y entonces decimos para un parámetro de entrada x
pues aquí es donde escribimos la fórmula de la sigmoide que si recuerdan era 1 partido 1 más np el número e
elevado a menos x.
Esta de aquí es la función sigmoide que si recuerdan tenía la forma de s
donde el número de entrada se mapea o sea se distorsiona un rango que va de 0 a 1.
Eso lo podemos visualizar fácilmente, podríamos coger aquí y hacer uso de la librería matplotlib.
Primero definimos una variable x que sea un lean space, si le dan a tabulador te autocompleta las funciones.
Creamos una variable que vaya de menos 5 a 5 de forma lineal y genere 100 valores.
Esto de aquí es básicamente un vector donde tenemos 100 valores que van de menos 5 a 5.
Pues con esto simplemente podemos coger y decir ahora hazme un plot donde en el eje x me pone esta variable que hemos creado
el barra x y en el eje y pues el resultado de pasar por la función sigmoide a barra x le damos a ejecutar
y aquí tenemos nuestra función sigmoide. Recuerdan la forma ¿no?
Muy bien, entonces dicho esto podemos adelantar un poco de trabajo y podemos adelantarnos a lo que viene después.
Recuerdan la red neuronal va a implementar el algoritmo de backpropagation y dentro del algoritmo de backpropagation
una de las derivadas parciales que calculábamos era la derivada de la función de activación.
Eso significa que en algún momento tendremos que definir cuál es la derivada de la función sigmoide
y eso básicamente lo podemos hacer aquí mismo.
Por comodidad podemos coger esto y convertirlo en un par de funciones,
vete a usar solamente una función, podemos decir que esto es un par donde aquí vamos a definir otra función
que sea la derivada de la función sigmoide.
Si recuerdan la derivada de la función sigmoide es simplemente el valor de entrada por 1 menos el valor de entrada, este de aquí.
Entonces ahora cada vez que queramos referirnos a la función sigmoide,
si queremos acceder a la función sigmoide tenemos que acceder de la variable sig al índice 0.
Ejecutamos y efectivamente funciona como debe. Si ponemos el 1 vemos su derivada.
Muy bien, esta de aquí es simplemente la función sigmoide, pero si recuerdan teníamos más, ya lo hemos dicho.
Tenemos la relu, tenemos la tangente hiperbólica, las podríamos definir aunque en este caso solo vamos a trabajar con una.
Por ejemplo la función relu si recuerdan simplemente era decir el máximo entre 0 y x,
porque si recuerdan era una función cuyo dominio positivo era simplemente el propio valor de entrada,
es decir si tú le metes 3 sale 3, pero si tú le metes un valor negativo menos 3 sale 0,
es decir que todos los valores negativos, todos los valores que sean menor que 0 van a ser 0,
con lo cual es lo mismo que decir que va a ser el máximo entre 0 y x.
De hecho podríamos visualizar esta misma función, quitamos el índice que nos hemos definido,
y efectivamente aquí vemos la forma de la función relu.
De esta manera tú podrías empezar a definir diferentes funciones de activación a tu gusto buscándolas en internet
y pues probar a ver qué tal funcionan una vez tengamos implementada la función de activación, la red neuronal.
Una vez hemos hecho esto ya podemos empezar a diseñar nuestra red neuronal,
y básicamente pues tú podrías coger y decir bueno hemos creado nuestra clase que nos permite crear capas,
con lo cual podemos empezar a decir bueno pues l0, una variable, vamos a llamarla layer 0, capa 0,
pues llama a la función neural, a la clase neural layer, crea un objeto de tipo capa,
donde el número de conexiones pues sería de entrada P, o sea el número de conexiones de entrada sería la variable P,
y luego pues tendríamos cuatro neuronas y una función de activación sigmoide que la hemos definido arriba,
l1 sería igual a neural layer, creamos otra capa, donde ahora sería 4 porque la l0 hemos definido que eran cuatro neuronas,
pues el número de conexiones es 4 y ahora tendríamos 8 y le podríamos poner otra sigmoide, etc, etc.
Esto es una forma válida de hacer esto y de ir creando tu capa, tu red neuronal, capa por capa, l1, l2, l3, l4, todas las que quieras,
pero bueno, al fin y al cabo tenemos que hacer el código elegante, bonito,
y parece que el haber creado la clase neural layer nos invita a hacer todo esto de forma iterativa de una manera mucho más cómoda,
y ese va a ser el caso, vamos a definir una función que es la que se va a encargar de crear nuestra red neuronal,
y la va a empezar a crear en función de una variable que vamos a llamar, por ejemplo, topology, topología de la red,
donde vamos a definir simplemente el número de neuronas que tiene la red, por ejemplo podríamos decir que la primera va a ser P,
el número de atributos de entrada que tiene nuestro datos puede pasar el número de neuronas que tenga nuestra primera capa,
y luego pues podemos decir de forma arbitraria que la siguiente capa va a tener 4, la siguiente 8, la siguiente 16, y 8, y 4, y 1, ¿vale?
La última capa es solamente una neurona, la neurona de salida, porque el resultado que queremos generar es un resultado binario,
o es de una clase, o es de la otra, o 0, o 1.
En ese caso tendríamos ya nuestro vector topology, que digo, el número de neuronas que he elegido ha sido de forma arbitraria,
y ya veremos en un futuro cómo se podrían decidir estos valores, ¿vale?
Y al igual que eso también podríamos tener otro vector que definiera qué funciones de activaciones vamos a utilizar en cada capa,
o qué learning rate vamos a utilizar en cada capa, que también tiene su importancia,
pero por simplicidad vamos a utilizar simplemente el número de neuronas como el único hiperparámetro que vamos pariendo en este caso.
Entonces a esta función le vamos a pasar este vector topology, y también le vamos a definir cuál es la función de activación que van a tener todas las capas.
Recordemos que no es necesario que todas las capas tengan la misma función de activación, en este caso de nuevo lo hacemos por simplicidad.
Entonces, una vez tenemos esto, pues podemos definir aquí un bucle for que itere sobre, a ver, que vaya en el rango de...
Bueno, de hecho podemos coger la función enumerate.
La función enumerate básicamente lo que hace es, te da tanto el índice en el vector que estás recorriendo como el objeto, que en este caso serían estos números aquí.
Entonces vamos a coger y vamos a enumerate el vector topology.
Con esto, pues directamente podríamos coger y decir, vale, estamos recorriendo nuestro vector topology,
podemos crear un vector que vamos a llamar nn, neural network, que va a ser un vector donde vamos a contener todas las capas de nuestra red neuronal,
va a ser la estructura de datos que sostenga a cada una de las capas que van a conformar nuestra red, y entonces podemos decir, mira,
dentro de este vector neural network vamos a añadir, usamos la función append, una neural layer, creamos un objeto de la clase neural layer,
cuya primera capa, por ejemplo, tenga el número de conexiones que pertenezca al vector topology en la posición L,
que L es el índice que vamos a recorrer durante todo este bucle form.
Y vamos a tener cuantas neuronas, pues vamos a tener tantas neuronas como hayan marcado en L más 1,
es decir, la primera capa oculta que vamos a añadir a nuestro vector nn va a ser la correspondiente a la primera capa oculta,
que va a tener cuatro neuronas y conexiones de entrada definidas por P.
Y la función de activación que vamos a utilizar es activation function, la que viene por el parámetro de aquí arriba.
Ya me estoy dando cuenta de que esto, como hemos puesto L más 1 aquí, va a causar un overflow en el vector, por supuesto que sí,
entonces simplemente lo que vamos a hacer aquí es decirle que recorra todo hasta el último valor,
descartando así el último valor.
Una vez hemos recorrido todo este for dentro de nuestra variable nn ya tendríamos que tener todas nuestras capas creadas,
con lo cual hemos automatizado todo el proceso y es mucho más cómodo.
Vamos a ver si esto está funcionando correctamente, primero ejecutamos la celda, parece que no hay error,
creo que no se va a ver ningún error hasta que no llamemos a la función, así que vamos a crear una red,
create nn, le pasamos el vector topology, que de hecho tiene más sentido que lo bajemos aquí abajo.
Entonces llamamos a esta función con el vector topology y la función de activación decimos la sigmoide.
En este caso vemos efectivamente que está funcionando y lo que recibimos es un vector que tiene 6 capas ocultas
correspondientes a todas estas aquí y efectivamente cada uno de estos es un objeto del tipo neural layer
que es el objeto que hemos definido.
Con esto pues ya lo que tendríamos sería nuestra red neuronal creada, es decir la estructura de datos
que soporta toda nuestra red neuronal, pero de momento no hemos implementado nada de la lógica que hace falta
para entrenar a este tipo de redes, ahora mismo no sirve para nada, de hecho no podrías hacer nada con ella.
Vamos por tanto a pasar a ver todo ese código que nos va a permitir entrenar a nuestra red neuronal.
Creamos una nueva celda y aquí vamos a empezar a definir una función que vamos a llamar train,
que es donde se va a ejecutar todo el código que se va a encargar de entrenar a nuestra red neuronal.
Recordemos que el paso de entrenar a la red neuronal se puede considerar que tiene 3 elementos fundamentales.
El primero es un paso hacia adelante, tú le vas a mostrar a la red neuronal un parámetro de entrada,
un tipo de dato de entrada, un dato de salida que es el resultado que quieres obtener
y ahí la red neuronal va a procesar hacia adelante por todas sus capas, por todas sus neuronas,
ejecutando sumas ponderadas y funciones de activación, hasta llegar al final y escupir un valor.
En este caso, como no está entrenada, el valor que va a escupir va a ser un valor aleatorio,
pero lo normal sería que con el paso del entrenamiento este valor se fuera asemejando al de nuestro vector i.
Ese sería el primer elemento, ese paso hacia adelante de la información.
El segundo elemento sería, una vez tienes un resultado, lo compararías con el vector y real, el resultado real o esperado.
Y ahí compararías haciendo uso de la función de coste, la función de coste es la que nos dice cómo se diferencian ambos resultados.
Y eso te va a generar un error, ese error lo vamos a utilizar para hacer una propagación hacia atrás,
el algoritmo de pack propagation, para así calcular las derivadas parciales que son las que nos van a permitir obtener
esas derivadas parciales que te cuentan la información necesaria para ejecutar la tercera pieza,
que es el algoritmo del descenso del gradiente, que el descenso del gradiente básicamente es lo que te va a permitir optimizar
esa función de coste y por tanto te va a permitir entrenar a tu red.
Esto debería estar ya claro después de tantos vídeos y vamos a ver cómo se haría.
Entonces, vamos a hacer uso, nos vamos a llevar nuestra función que hemos creado aquí abajo,
vamos a llevarnos todo esto, vamos a poner la celda de abajo, código ordenado siempre,
y vamos también a definir un elemento importante que acabamos de mencionar que es la función de coste.
En este caso podemos definir una función de coste como el error cuadrático medio,
que si recuerdan se definía de esta manera.
Vamos a hacer igual que hicimos antes, lo de tener una función anónima,
y el error cuadrático medio básicamente es para dos valores de entrada en este caso,
por ejemplo la Y predicha y la Y real, el valor que ha predicho nuestra red neuronal y el valor real,
por lo que vamos a hacer es calcular la diferencia, ver en cuanto difiere un valor del otro,
y vamos a coger estas diferencias y las vamos a llevar al cuadrado,
como no vamos a tener solamente un único elemento en este vector, sino pueden que haya muchos,
por lo que calculamos es la media de este vector.
Por eso se llama el error cuadrático medio, ahí están todos los elementos.
Y de hecho podemos volver a repetir el proceso que hemos hecho mental antes,
si en el algoritmo de backpropagation vamos a necesitar en algún momento definir cuál es la derivada de la función de coste,
pues tiene sentido ya adelantarnos y redactarla aquí, implementarla aquí,
de la misma forma que lo hicimos antes con un par de funciones.
Entonces vamos a coger, vamos a definir que la derivada de esta función, si la buscan,
pues es simplemente la diferencia entre el valor predicho y el valor real.
Este de aquí sería su derivada.
Una vez tenemos esto definido, pues ya podemos empezar a definir nuestra función train.
En train es donde va a pasar todo, tanto el forward pass como el backward pass,
como el gradient descent, el descenso del gradiente.
Entonces para eso pues tenemos que pasarle como parámetro primero la red neuronal que hayamos creado,
que aquí esto lo hemos copiado pero no lo hemos asignado ninguna variable,
vamos a crear una variable que se llame neural net, red neuronal,
y se la vamos a pasar como parámetro, neural net.
Luego, ¿qué más tendríamos que pasarle a esta función?
Pues los datos de entrada y los datos de salida, nuestro vector x y nuestro vector y,
que hemos definido aquí arriba, x e y.
¿Qué otro elemento le tendríamos que pasar?
Pues la función de coste, sería interesante pasárselo.
Y también tenemos que definir, recuerden que en el gradient descent había un hiperparámetro
que era el learning rate, el ratio de aprendizaje,
que le podemos poner un valor de momento de 0.5 y luego ya probamos a ver si funciona o no.
Recuerden que el learning rate es básicamente un factor por el cual multiplicamos al vector gradiente
en el descenso del gradiente y que te permite determinar en qué grado estamos actualizando nuestros parámetros,
actualizándolo en base a la información que nos proporciona el gradiente.
Un valor muy grande puede hacer que tu red nunca converja porque empieza a saltar
y se va al infinito y te puede producir un gradient exploding o exploding gradient
y un valor muy pequeño puede hacer que el proceso de entrenamiento sea demasiado, demasiado lento.
¿Vale? Siempre es muy importante definir un buen valor del learning rate
y de hecho ya haré un video especial hablando de esto porque tiene bastante chicha.
Entonces, vamos a ir por parte, vamos a intentar hacer este trozo de código con bastante cabeza
para que lo puedan entender y no se pierdan porque básicamente aquí es donde está toda la chicha del tutorial.
Entonces vamos a empezar haciendo el forward pass.
El, uy va, no es de más, vale, el forward pass.
En el forward pass lo único que tenemos que hacer es coger nuestro vector de entrada y empezar a pasarlo capa por capa
ejecutando las operaciones que se realizan en cada una de las neuronas, que si recuerdan eran de dos tipos.
Suma ponderada, donde multiplicamos el valor de entrada x por w, le sumamos el parámetro de vallas
y luego la siguiente operación era pasar esa suma ponderada a una función de activación,
la función de activación que está en nuestra capa. Por ejemplo, si quisiéramos hacer esto de la primera capa
pues simplemente tendríamos que decir la suma ponderada, vamos a llamarle z, como recordarán que lo definimos en el video de back propagation,
pues vamos a decir que la suma ponderada es igual a x, el vector de entrada, la matriz de entrada, multiplicado matricialmente,
para eso usamos la roba, por lo que tengamos en la capa número 1, en neural net, la primera capa, su parámetro w,
el parámetro de los pesos, y a esto luego le tenemos que coger neural net y cogemos, de la primera capa, y cogemos el parámetro b,
que eso sería el parámetro de vallas, pues aquí en esta línea de código ya hemos implementado la suma ponderada que se ejecuta en la primera capa,
después lo único que tenemos que hacer es crear una variable a, que se refiere a la activación, que simplemente es coger,
pues de nuevo en esta capa, neural net, la capa 0, la función de activación que habíamos definido como activation function,
y pasarle la suma ponderada, eso se va a guardar en la que básicamente es la salida de la capa 1, ahora si fuéramos a hacer la capa número 2,
pues sería empezar de nuevo y multiplicaríamos a la activación de la capa anterior por neural net, habría que repetir todo este proceso,
de nuevo esto nos invita a hacerlo de forma iterativa, en vez de ir capa por capa, pues podemos hacer un bucle for que vaya iterando todas las capas,
esto lo vamos a hacer, pues eso, definiendo un bucle for, que básicamente coja y diga, para L y layer, in enumerate el vector neural net,
es decir que recorra todas las capas en nuestra red neuronal, y entonces vamos a utilizar el índice L para recorrer cada una de las capas en nuestra red neuronal,
en este caso la X solo serviría para la primera capa oculta, porque es la única que tiene previasa capa donde encontramos la capa de entrada,
pero ya cuando estemos en la capa número 2 tendríamos que coger el output de la capa número 1 y cuando estemos en la 3 el output de la capa número 2,
con lo cual aquí lo que nos interesa en realidad es crear un vector donde vayamos guardando esa información,
y como puede ser útil lo que vamos a hacer en vez de guardar solamente la activación de la capa 1, de la capa 2, de la capa 3,
lo que vamos a hacer es guardar los pares de información donde guardemos tanto el valor de la suma ponderada de la capa 0 por ejemplo y también el valor de activación,
y luego pues lo mismo con la capa 1, esto sería Z1 y la capa 1, etcétera, entonces este vector lo vamos a dejar vacío,
y esta información de aquí una vez que tienes procesada la activación de la neuronal que sería el output de la capa correspondiente,
pues simplemente vamos a hacer un append y le vamos a meter por los valores de Z y los valores de A, la suma ponderada y la activación,
con lo cual pues aquí ya simplemente en vez de hablar de X podríamos referirnos a el output, el último output que hayamos metido,
es decir con menos 1 nos referimos al último valor que hayamos introducido en nuestro vector de salida,
y como nos va a devolver un par pues podemos coger el que nos hace falta que en este caso sería el índice 1, es decir la activación,
con esto, con estas tres líneas de código que en realidad podrían ser dos fácilmente e incluso una,
hemos hecho ya el forward pass, todo el procesamiento hacia adelante de nuestra red neuronal,
y de hecho podríamos comprobarlo, podríamos ejecutar esto ahora mismo y ver efectivamente como esto nos va a generar un resultado
que como no hay ningún entrenamiento pues va a ser completamente aleatorio, pero podemos probar a ver qué nota,
podemos decirle haznos un print de el valor de salida, entonces primero ejecutamos esta a ver si no hay ningún error, perfecto,
y ahora vamos a llamar a la función, vamos a llamar a neuralnet, le pasamos la i, le pasamos la i, le pasamos la función de coste y 0.5,
le damos a train y ahora sí salen los errores, nos dice que lista fuera de rango, lista fuera de rango,
porque aquí estamos recorriendo neuralnet, aquí hemos definido que vamos a coger el output,
vamos a coger la activación que tengamos en el vector output pero en la primera iteración este vector está vacío,
con lo cual realmente tendríamos que aquí ya definirle un par de datos que se refieran a la primera capa,
tenemos la capa de entrada donde la suma ponderada no existe porque es la capa de entrada y el output de esa capa de entrada sería el vector x,
con esto le damos de nuevo y tenemos otro error diferente que nos dice que tuple object is not callable porque
que no es llamable el objeto tupla porque aquí estamos usando la función de activación,
pero recordemos que esto es la sigmoide que hemos definido como un par de funciones,
realmente aquí estamos definiendo que queremos utilizar la función de activación que estaba en el índice 0,
ejecutamos esto y ahora sí que sí tenemos un resultado, un resultado que de hecho como hemos dicho es completamente aleatorio,
esto sería para todos los valores pues obtenemos casi el mismo resultado que como digo es aleatorio,
de hecho esto de nuevo podríamos adusar hasta la función de activación que hemos pasado como parámetro,
hemos dicho que primero le pasamos el valor predicho, es decir este de aquí,
y luego le pasamos el valor real que lo tenemos en el vector y y esto lo podemos imprimir,
y de nuevo la función de activación está definida como un par de funciones así que índice 0,
ejecutamos y ahí estaría, este sería nuestro error que bueno es un error completamente aleatorio
porque ahora mismo nuestra función nuestra red neuronal pues no está siendo entrenada.
Una vez tenemos hecho esto pasamos a la siguiente parte, el siguiente bloque sería ir para atrás,
una vez tenemos el error ya calculado porque hemos obtenido un valor que es aleatorio,
hemos comparado los dos vectores de salida pues ya podemos aplicar el algoritmo de back propagation hacia atrás,
eso significa que vamos a entrenar a nuestra red, pero claro en este caso pues lo hemos hecho un poco chapucero
porque esta función de aquí realmente esta lógica que hemos implementado aquí la podríamos utilizar a veces también
no sólo para hacer un forward pass y obtener el resultado para luego entrenar a la red,
a lo mejor simplemente queremos obtener un resultado, hacer una predicción sin necesidad de entrenar a la red,
con lo cual pues lo suyo sería separar los dos funciones diferentes,
pero en nuestro caso pues lo que podemos hacer es crear una variable booleana que llamaremos train
y vamos a poner como en true y básicamente pues usamos si esta variable o sea si train está activada
pues ahora si ejecutaríamos pues el backward pass que realmente esto sería el training
y el backward pass y el gradient descent que en combinación a esto pues sería el training,
si esta variable la establecemos en falso esto nunca se ejecutará y simplemente estaríamos ejecutando esto para hacer una predicción,
con lo cual mira, estaba chapucero pero lo hemos solucionado bien.
Entonces vamos a implementar ahora backpropagation y esto básicamente es implementar las funciones, las fórmulas que vimos en el último vídeo,
esas fórmulas lo que te van a dar son las derivadas parciales que necesitamos para el algoritmo del gradient descent,
esas derivadas parciales pues son la de los parámetros W y B, el bias respecto al coste,
entonces para implementar eso si recuerdan lo que hacíamos era ir propagando el error de la red hacia atrás
y vamos calculando una variable que denominábamos delta, no me acuerdo si se llamaba delta, bueno esta variable de aquí,
sí creo que era delta, vale, confirmo que era delta este símbolo de aquí,
pues íbamos definiendo, íbamos calculando nuestras deltas según íbamos hacia atrás porque teníamos realmente dos fórmulas que nos permitían calcularlo,
una de las fórmulas era el que te daba la delta para la última capa que era un caso especial porque realmente en la última capa el error viene directamente de la función de coste
y luego a partir de ahí tenías otra fórmula que te daba cómo calcular la delta en función de la delta de la capa anterior, del error de la capa anterior,
que esa es la que vamos a utilizar para el resto de las capas, pues simplemente tenemos que implementar esa fórmula y ya tendríamos nuestras derivadas parciales.
Para eso lo que vamos a hacer es coger aquí, vamos a definir esto como un vector,
donde vamos a ir almacenando todas las deltas que vayamos calculando y entonces como vamos a hacer un pase hacia atrás,
pues de nuevo vamos a hacer un bucle for, pero en este caso que vaya de atrás hacia delante o hacia atrás, o sea de delante a atrás, ya me entendéis.
Entonces para eso lo que vamos a hacer es coger y definir de nuevo nuestras variables favoritas l y layer que van a recorrer en numerate, bueno no,
en este caso de hecho vamos a tirar solamente con el índice porque no nos va a hacer falta la capa y vamos a coger y vamos a utilizar el rango
que vaya desde 0 hasta la longitud del número de capas que tengamos en nuestra red neuronal, y esto lo vamos a hacer hacia atrás,
para eso utilizamos la función reversed, así estaría. Ejecutamos y bueno claro nota error porque no hemos implementado nada en este bucle.
Vale, entonces en este caso pues como hemos dicho tenemos dos casos diferentes, uno sería el caso en el que tengamos,
que nos encontramos en la última capa donde delta se calcula de una manera y otro sería en el resto de capas que se calculará de otra manera diferente.
Para eso lo que vamos a hacer es lo siguiente, vamos a definir un if que nos diga si el índice l es igual al número de capas neural net,
la longitud del vector que tiene el número de capas menos 1 porque los índices empiezan en 0, pues eso significa que estamos en la última capa,
con lo cual aquí pues vamos a calcular delta última capa y si no pues será calcular delta respecto a capa previa.
Estaría guay ejecutar ahora aquí y que funcione solo a base de comentarios. A lo mejor en un futuro con machine learning podemos conseguir esto,
programación semántica. Bueno, entonces aquí simplemente es donde tenemos que implementar dos de las cuatro fórmulas que vimos en el tutorial de las fórmulas de backpropagation.
Antes que nada vamos a coger el valor de la suma ponderada y la A y la activación que nos van a hacer falta y esa las obtenemos del vector output que hemos definido antes,
con lo cual en este caso sería output, claro aquí la L se refiere al vector de capas que tiene nuestra red neuronal donde realmente no hemos puesto la primera capa,
es solamente las capas ocultas y la última capa, con lo cual está un poco mal el tema de los índices pero simplemente con referirnos a L más 1 pues ya estaríamos cogiendo el índice correcto,
entonces sería L más 1 a más 1. Esto lo podemos comprobar si por ejemplo imprimimos la forma a punto shape de la activación de la última capa,
pues esto nos debería de dar que tiene un tamaño 501. A ver si no me equivoco, ah bueno esto está dando error aquí, vamos a comentar todo esto para que no dé error.
Tengo la tecla tapada por el micrófono, qué hago, bueno pues lo escribo a mano, así, dios mío, vale, tenemos un resultado, qué ha pasado aquí,
ah vale, es cierto que output era un par, así que aquí nos tendríamos que referir, Z sería el índice 0 y la activación sería el índice 1.
Vale, efectivamente vale, vemos que tiene el tamaño 501, 504, 508, 516, estas son las activaciones, estos son los tamaños que tendrían nuestras salidas por cada capa si fuéramos hacia atrás,
vale, perfecto, entonces ahora simplemente tenemos que implementar aquí las fórmulas que hemos dicho. Si recordamos la fórmula que tenemos que implementar para calcular el coste con respecto,
para calcular el error de la última capa con respecto a el coste, la delta, era básicamente, vamos a coger, vamos a poner deltas.append que nos va a hacer falta,
bueno de hecho no, en vez de append te va a añadir el elemento al final del array, pero en este caso como estamos yendo hacia atrás quiero que nos lo ponga al principio,
entonces para eso vamos a usar la función insert el índice 0, vale, aquí es donde vamos a implementar nuestra fórmula.
Si recuerdas nuestra fórmula en la última capa, hacemos memoria, bueno ustedes no hacen memoria porque lo están viendo en pantalla ahora mismo,
sería que el valor lo obteníamos en base a la derivada de la función de coste y la derivada de la función de activación en la última capa,
con eso es con lo que obteníamos nuestra delta 0, pues vamos a implementarlo, esto sería simplemente pues coger y decir nuestra función de coste,
a ver la llamamos así, como estaba definida, l2 cost nuestra función de coste, su derivada la teníamos en el índice 1,
pues esto lo llamamos para los valores a, que sería el output en la última capa, que sería la salida de nuestra red neuronal y el valor y,
vale, el vector con los valores reales esperados de salida y esto lo multiplicamos por la derivada de la función de activación de la última capa,
es decir, en este caso pues estaríamos refiriéndonos a neural net l, esa sería la última capa, accedemos al atributo función de activación
y recordemos que esto era un par que tendría en el índice 0 la función de activación y en el 1 la derivada,
con lo cual pues calculamos la derivada en el punto a, en el punto equivalente a la activación de la neurona,
esto de aquí es simplemente la fórmula con la cual vamos a calcular delta 0, fácil, lo ejecutamos y parece que no hay errores,
vamos a ver si podemos imprimir el valor de esta delta, aunque no nos va a decir mucho de momento, bueno, aquí lo tendríamos,
vamos a ver si su forma es correcta, efectivamente 501 tiene sentido para estos valores de delta.
Muy bien, ahí ya tendríamos calculado la mitad del algoritmo de backpropagation, si quisiéramos ahora calcular la otra,
pues simplemente sería casi más de lo mismo, en este caso, si recuerdan la fórmula para calcular la delta en función de la delta de la capa posterior,
pues simplemente era casi todo igual, con la única diferencia de que esto de aquí hacía uso de la delta,
de la delta que habíamos calculado previamente, de la capa anterior, que en este caso la tendríamos guardada en la posición 0 de nuestro vector de deltas,
súper sencillo, y esto está multiplicado matricialmente por el vector de pesos W correspondiente a la capa siguiente en la que nos encontramos ahora,
estamos en la capa L, pues tendríamos que mirar hacia la capa L más 1, a las conexiones entre nuestra capa y la siguiente,
porque nuestro error va a estar en la capa siguiente, y lo que queremos hacer es moverlo hacia atrás, hacer un backpropagation del error,
con lo cual ahí estaríamos multiplicando por la matriz W de la capa L más 1, está de aquí, y con esto tendríamos implementado backpropagation,
con estas dos fórmulas, pues ya tenemos que el forward pass son tres fórmulas, el backpropagation son dos fórmulas, dos líneas de código,
parece todo bastante sencillo, pues sólo nos faltaría implementar una última cosa, sería el gradient descent, hacer uso de estas deltas para optimizar los parámetros de nuestra red,
lo que podemos hacer es como gradient descent requiere que pasemos de nuevo por cada una de las capas de nuestra red neuronal y vayamos actualizando los parámetros,
en vez de hacer otro bucle for que recorra todas las capas hacia adelante actualizando esos valores, podemos directamente mientras estamos haciendo el pass hacia atrás,
también ir actualizando nuestros vectores gradientes, o sea nuestros parámetros en función de los vectores gradientes, y eso se hace muy fácilmente,
podemos coger aquí decir neural net, la capa en la que nos encontramos, que es la capa L, y cogemos por ejemplo el parámetro B,
y en este caso si recuerdan lo único que teníamos que hacer era pues a este valor, el mismo valor, tendríamos que restarle el valor de B,
de los parámetros B de nuestra capa con respecto al coste, y eso lo obteníamos con su derivada parcial que en función de delta simplemente era delta,
era una de las fórmulas que sacamos, recuerdan que era delta por 1, con lo cual es delta,
entonces simplemente lo que tendríamos que hacer aquí sería coger delta y restárselo a este valor,
lo único que hay que tener en cuenta aquí es que el valor de delta que vamos a utilizar,
pues por ejemplo en este caso va a tener la forma la que vimos 1, 500, porque tenemos 500 valores de N,
en este caso por lo que tendríamos que coger sería su valor medio, porque por ejemplo en la última capa el parámetro beta,
el parámetro bias solamente va a ser 1, 1, un único valor, con lo cual tendríamos que restarle la media de todos los valores.
Todo lo que vamos a hacer es coger deltas, el elemento deltas que tenemos en la capa nos podemos referir,
claro sería el último que hemos insertado en nuestro vector de deltas porque estamos dentro del bucle,
esto está mal colocado, estaría dentro del bucle, y cogemos pues este valor de aquí,
queremos calcular su media solamente la dimensión en el eje 0, porque si el vector deltas tiene 500 elementos
y la dimensión del número de bayas, pues queremos que sólo se aplique a la media en el eje 0,
en el número de elementos, y le vamos a decir que mantenga las dimensiones porque si no nos lo va a convertir
en una serie y nos va atrás a tocar todos los índices, vamos a poner esto aquí a true,
y con esto pues lo único que nos faltaría sería multiplicar este valor por el learning rate
que le hemos pasado como hiperparámetro aquí arriba, con esto pues ya tendríamos calculado
el descenso del gradiente para el algoritmo de bayas, con esto ya estaríamos optimizando el coste
en función del parámetro de bayas, y si queremos hacer lo mismo con el parámetro W hay que tener
una cosita en cuenta, y es que nosotros aquí en este paso estaremos actualizando el parámetro W,
todavía no hemos puesto la fórmula correcta, pero bueno, estaríamos actualizando el parámetro W,
y luego en la siguiente iteración del bucle for cuando vayamos a la siguiente capa,
aquí hacemos referencia, aquí estamos haciendo referencia a la W de la capa posterior, es decir,
la que ya hemos actualizado con gradient descent, y eso es problemático porque tú no puedes estar
al mismo tiempo modificando un valor a la vez que lo estás optimizando, o sea no puedes hacer back
propagation en función de unos valores que a la vez estás modificando con la optimización,
eso significa que aquí cuando hacemos referencia a la variable W deberíamos de guardarla temporalmente
para hacer la uso en la siguiente iteración, aquí podríamos definir una variable que sea barra bajo W
donde guardamos este valor de aquí, por ejemplo así, y entonces aquí en vez de utilizar esto
utilizaríamos el valor que hemos guardado en la variable esta, con lo cual ahora mismo aquí da igual
que estemos modificando este valor de aquí, el W en este caso, porque luego no nos vamos a referir a él
porque ya nos estamos refiriendo simplemente a la variable auxiliar que estamos utilizando,
que hemos guardado aquí. Perfecto, pues lo único que nos faltaría sería colocar la fórmula correcta
para W, que si recuerdan la fórmula en este caso era simplemente coger las deltas y multiplicarlas
por la activación de la capa anterior, si estamos en la capa L tendríamos que coger las conexiones
de la capa L-1, la fórmula que están viendo ahora en pantalla, esto se hace simplemente cogiendo
de nuestro vector de outputs el elemento de la capa L, porque si aquí estamos tratando con los elementos de la capa
en este vector estamos refiriéndonos a L-1, pues si queremos coger los de la capa anterior serían los de la capa L
aquí de nuevo insisto en que ha sido un poco raro cómo hemos cogido los índices finalmente
porque puede llegar a ser confuso, pero bueno, lo entienden, nos estamos refiriendo a los índices de la capa anterior
a la que estamos trabajando actualmente, y entonces de aquí lo que vamos a coger es el output que lo teníamos
en el índice 1 y esto lo vamos a multiplicar matricialmente, con esto ya tendríamos implementado las tres partes
necesarias dentro de una red neuronal, el forward pass, el backward pass y la optimización en base al gradient descent
todo esto multiplicado el learning rate, y finalmente aquí lo que devolveríamos, como recordarán si estamos
en modo predicción, si esto no se ejecutara, igualmente lo que querríamos devolver sería el valor predicho
que no lo tenemos guardado en ninguna variable, pues sería, en cualquier caso lo tendríamos en el output
sería de la última capa, de la menos 1 el valor en el índice 1, con esto vamos a ejecutarlo a ver si hay algún error
parece que faltan dos puntos por aquí, ahora seguramente tengamos un montón de fallos de índices y problemas
vamos a ver qué pasa, vamos a intentar solucionarlos todos, vale en este caso, este barra baja W, en realidad este índice está mal
tendría que ser L, porque por ejemplo aquí estaríamos, imagínate que estás en la iteración L, tú esto lo guardarías en esta variable
te vendrías aquí, y entonces en la siguiente iteración cuando ya fueras a utilizar la barra baja W
ya estaríamos en L menos 1 porque estamos yendo hacia atrás, con lo cual el valor L es el valor L más 1 que estamos buscando
esto había que quitarle el más 1 que teníamos aquí, ejecutamos de nuevo, problemas con los índices
vale, este error sí me suena, esto sé por qué está pasando y es porque básicamente, seguro me juego la mano, que la variable esta de aquí X, Y
pues lo normal es que tenga la forma 502, N por P que hemos definido aquí arriba, ejecutamos, correcto
pero nuestra Y queremos que sea del tamaño 501, pero seguramente aquí va a ser 500, porque te devuelve una serie
en vez de devolverte una matriz columna, lo que te está devolviendo es una serie, con lo cual simplemente pues, y eso genera muchos problemas
como suelen ser habituales, como se habrán dado cuenta, entonces para solucionarlos simplemente es muy sencillo
vamos a venir aquí y vamos a coger y decir, vale, pues Y va a ser igual a Y con todos los valores
y en la columna le vamos a añadir una nueva dimensión que va a ser la dimensión 1, que esto es con new axis
si hacemos un print ahora de su forma, vale, parece que está bien, parece que está bien, pero claro ahora da error el scatter aquí
porque ya no podemos hacer esto, sino que tenemos que coger y poner aquí 2 puntos, 0, vale, y esto tenemos que copiarlo
y añadirlo tanto como aquí, como aquí, como aquí, porque como hemos añadido una nueva dimensión, pues tenemos que referirnos a ella
vale, perfecto, vale, ya estaría todo esto funcionando, creo que no hace falta ejecutar nada realmente, excepto esta parte de aquí
vale, se ha solucionado el error, antes ponía 500 500, ahora tenemos otro, dice shapes 504 y 501 no están alineadas
esto seguro que va con transpuestas, transpuestas y cositas así, vamos a intentar buscar el error
vamos a ver si encontramos el error, esto se está produciendo en W, vale, pues aquí estamos multiplicando a ver qué valores
por ejemplo, out l1 shape, vamos a ver su valor, qué forma tiene y también vamos a ver deltas ceros, qué valor tiene, a ver si coinciden
vale, 504, 501, para que esto se multiplique y esté bien, esta de aquí debería estar transpuesta, debería ser básicamente así
que tiene que estar, estas dos tienen que ser iguales, entonces básicamente lo que tendríamos que hacer aquí es coger este output y poner la transpuesta
vale, parece que ha avanzado, vale, cuando hacemos esto, en la siguiente iteración, cuando nos referimos a delta cero, nos produce otro error con los índices
vamos a solucionarlo, esto es lo coñazo de estar trabajando siempre con vectores y matrices, los índices, a ver, tenemos que, aquí
multiplicamos deltas ceros punto shape y el valor de, vamos a ver qué valor tiene aquí, ejecutamos de nuevo, 501, 41, tiene pinta de que esta de aquí
debería estar transpuesta, es decir, que esto debería ser w transpuesta, vamos a comprobarlo, efectivamente, ahora se ejecuta hasta el final
alright, parece que va todo bastante bien, ahora solo falta que funcione, vamos a eliminar todos los prints que ya no nos hacen falta
esto lo podemos quitar también, esto lo podemos ejecutar, vale, al menos ya nos está escupiendo un resultado, perfecto
pues lo que vamos a hacer ahora es probar nuestra red neuronal y ver que funciona, vamos a iterar muchas veces la función train
y si funciona, pues deberíamos de ver como el resultado poco a poco se va acercando a lo que buscamos
voy a poner aquí un print de algo vacío para que no me escupa ese vector, para comprobar que la red funciona, pues tengo aquí un trozo de código
que ya tenía implementado, que nos va a permitir visualizar como se va realizando este entrenamiento dentro de la red
todavía no lo he probado, o sea, lo he probado en el código que tenía, pero no lo he probado con esta red neuronal aún, así que todo puede fallar
pero bueno, no he querido implementarlo porque realmente hay bastantes cosas ajenas de lo que viene siendo el tutorial
pero si puedo explicar un poco por encima que tenemos por aquí, vale, lo que estamos haciendo es crear de nuevo una red neuronal completamente nueva
utilizando, eso sí, la topología que hemos diseñado y la función sigmoide, hemos definido un vector vacío
donde vamos a ir guardando las diferentes costes según se vaya entrenando la red, para que podamos así graficar como el coste evoluciona
con respecto al tiempo, y lo que vamos a hacer ahora es iterar muchas veces mientras, muchas veces significa por ejemplo 1000
mientras llamamos a la función train que hemos implementado aquí arriba, esta de aquí, esta función de coste la tenemos que cambiar
que ahora se llama así en nuestro juego, y de nuevo aquí también habría que cambiarla, y creo que ya no hay que cambiarla en ningún lado más
vale, simplemente lo que estamos haciendo es llamar a train muchas veces y cada 25 iteraciones, cuando el módulo del número de iteraciones es igual a 0
es decir, cada 25 iteraciones, primero calculamos el coste, aquí también aparece, calculamos el coste, lo añadimos a nuestro vector
y lo que vamos a hacer aquí, esto básicamente va a generar una especie de malla de 50x50, eso es lo que viene a definir esta variable aquí, la resolución
pues una malla de 50x50 y en cada uno de esos puntos vamos a hacer una predicción, y como estamos haciendo una predicción he puesto la variable en falso
de nuestra red neuronal, así podemos visualizar en rojo y en azul toda la superficie que la red neuronal está considerando de una clase y de la otra
eso se va a entender más cuando lo vean, esas visualizaciones están realizando aquí, y esta parte de código simplemente lo que va a hacer es borrar el contenido
y volver a dibujarlo para que así se vea de forma animada, pues dicho esto vamos a darle a control enter y ver que sale, a ver, sale la primera, sale la primera
vale, vale, vale, vale, me da que no está funcionando, me da que no está funcionando porque esto se ha quedado parado y no parece que se esté entrenando nuestra red neuronal
vale, tiene que haber algún tipo de error, vamos a comprobar que no haya nada aquí que no esté bien, está funcionando aquí, create nn es la nuestra que hemos definido
ya vamos al train, train está entrenándose supuestamente, vamos a ver si tiene relación a lo mejor con el learning rate que hemos elegido, a lo mejor es muy bajito
podríamos coger aquí y decirle learning rate igual a por ejemplo 1, a ver si esto genera algo diferente
vale, parece que hay movimiento, pero no, este valor de aquí es muy sospechoso porque es muy exacto, 0.25, así que eso significa que tenemos algún tipo de error por algún lado
vale, ya he encontrado lo que estaba pasando y era una especie de combinación de un learning rate incorrecto, este de aquí, y quizás una topología demasiado compleja
lo cual me parece curioso porque tampoco es demasiado compleja, 4, 8, 16, 8, 4, 1, pero claro a lo mejor para la cantidad de datos que tenemos que son solamente 500 puntos
pues sí puede que sea compleja, entonces lo he podido solucionar simplemente mediante una topología más sencilla, he quitado todas estas capas, he dejado solamente 4, 8 y 1
con lo cual ejecutamos esta celda de nuevo, y ahora vamos a ir ajustando el learning rate, pero vamos a darle a ejecutar de nuevo, a ver qué pasa ahora
vale, vale, vale, vale, vemos que de nuevo el loss está por ahí aplanado, a ver si avanza, no, todavía no
vale, parece que no, puede que sea que el learning rate sea bajo, vamos a subirlo un poquito, le damos de nuevo
parece que baja, ahora sí que parece que está bajando, vemos como la región roja se empieza a acomodar a la nube de puntos
vale, vale, vale, ahí estaría, se acerca, se acerca, vamos poco a poco, lo tienes, lo tienes, vamos
vale, lo tengo limitado a 1000, vamos a ponerle incluso un learning rate más rápido, 0,05 y por si acaso vamos a incrementar aquí el número de iteraciones
para que no se nos pare tan rápido, le damos de nuevo, vale, parece que baja, supera la barrera esa que al principio le cuesta
ahí lo tenemos, ahí estaría, ahora sí que sí, vemos que el error casi baja a 0 y efectivamente la red neuronal ha encontrado
la combinación de parámetros que hace que las dos clases estén completamente separadas, en rojo y en azul
lo tenemos chicos, lo hemos conseguido, muy bien, pues si ha llegado hasta este punto la red neuronal debería estar funcionando
y debería tener un resultado similar, lo cual pues, oye mola, es tu primera red neuronal, a lo mejor sí, a lo mejor no
ponmelo abajo en los comentarios, me gustaría saberlo, y de hecho el reto a que si consiguen llegar a este punto al tutorial
prueben con diferentes tipos de datasets, en este caso hemos probado con lo de los círculos, pero les invito a probar un reto por ejemplo
que a lo mejor en vez de los puntos sea con espiral, buscaros la vida para encontrar cómo hacer esa nube de datos
o cualquier otra que les apetezca y prueben con la misma red neuronal pues a ver qué resultado consiguen
e incluso también pueden probar con más de dos clases, sería en vez de simplemente tener, pues a lo mejor poner dos neuronas
en la última capa, utilizar lo que se llama One Hot Encoding para definir cuál es el número de la capa y hasta ahí simplemente
probar con eso, les invito a probar a hacer alguna de estas cosas y si lo hacéis y conseguís entrenar un dataset diferente
pues que lo pongáis en twitter, me podéis buscar en twitter aquí dot ccv, seguramente está apareciendo aquí el twitter
eso espero, y lo ponéis ahí, lo publicáis y decís, oye Carlos mira me ha salido esto y este es el resultado que he conseguido
la verdad que me va a hacer un montón de ilusión ver que habéis conseguido llegar hasta aquí
y nada más, este ha sido el tutorial de hoy, un poquito más complejo de lo habitual, tampoco tan complejo como habéis visto
y como he explicado antes pues posiblemente haga una parte extensible a este tutorial
donde explique realmente cómo hacer una red neuronal de la forma en la que lo haríamos realmente
utilizando tensorflow o sklearn o alguna de estas tecnologías y nada más, básicamente esto es todo
si les gusta este tipo de tutoriales donde nos ponemos a picar código, déjenlo claro abajo dándole a like a este vídeo
comentad todas las dudas que tengáis, si habéis detectado algún error que se me haya escapado aunque esté funcionando
puede que haya algún error en el código, háganmelo saber, yo compartiré este notebook, seguramente este es el link ya en la descripción
para que lo podáis utilizar y modificar si queréis y nada más, podéis apoyar este tipo de vídeos como ya dije antes
mediante Patreon o si no pues simplemente comentarles a vuestro círculo de amigos, amigas, si tenéis alguien que le pueda
interesar este canal, pues decid que aquí estamos todos aprendiendo Inteligencia Artificial
nada más, como siempre, mi nombre es Carlos Santana, DOTCSV, un saludo y nos vemos en el siguiente vídeo