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.

¡Ey, espera! ¿Quieres aprender a programar una arquitectura de deep learning que te
permita coger una imagen como ésta y que a partir de ella te genera una imagen de
una flor realista como ésta? ¿Quieres aprender una arquitectura llamada
pix2pix, y que debería ser ya por todos conocidos, que te permite resolver
múltiples problemas en los que cogemos una imagen de entrada y la convertimos
en otra cosa? ¿Un tipo de modelo que realmente se podría aplicar a múltiples
otros problemas, como coger un mapa de segmentación y convertirlo en una imagen
realista, una imagen en blanco y negro, una imagen a color, una imagen de un
estilo a otro estilo, una imagen de borde a un objeto, etcétera. Múltiples
problemas. Todo esto lo podemos conseguir con un modelo que ya vimos en el vídeo
anterior de deep newt y que es el modelo pix2pix o su variante más avanzada el
modelo pix2pix hd. En este caso en el tutorial de hoy les voy a dar la
oportunidad de que ustedes implementen DT0 y utilizando tensorflow 2.0
vuestro propio modelo. Y no sólo eso, sino que también se está viendo este
tutorial durante el verano de 2019. Este tutorial te va a dar el conocimiento
suficiente para que puedas participar en esta competición de aquí, que saqué un
vídeo que creo que muy poca gente vio porque lo subí justamente el día en el
que youtube falló, en el que podrás ganarte gracias a dotcsv, a este canal y
a nvidia que me ha facilitado una tarjeta gráfica 2080 super para
sortear, en una competición en la que tendrás que usar el modelo pix2pix de
alguna manera creativa, original, de impacto, etcétera. Tenéis todos los
detalles en aquel vídeo que publiqué hace unas tres semanas y esta competición
estará abierta hasta el día 14 de septiembre por la noche, así que también
tienes tiempo para aprender y participar. Dicho esto, comenzamos con el tutorial.
Un par de apontes, este tutorial es un tutorial largo que casi dura dos horas y
en el que vamos a estar programando pues cosas de cierta complejidad, vale? Te
indico que sería muy buena idea que te vieras antes de ver este vídeo, el vídeo
en el que hablábamos de Deep Mute, que básicamente es un vídeo en el que de
manera encubierta me paso 30 minutos hablándote del modelo pix2pix a nivel
conceptual. A partir de ahí también podría ser una buena idea que le
echaras un vistazo al paper de este modelo y con eso ya tendrías todos los
conceptos teóricos y los conceptos intuitivos de lo que vamos a trabajar
hoy. En este caso para este tutorial he buscado un problema muy sencillo a
resolver porque no quiero pues solaparme con cualquier proyecto que realmente
vaya a ser presentado a esta competición. Así que el proyecto que he
buscado ha sido el de intentar generar imágenes de flores realistas a partir de
una imagen dada como entrada, que es la que va a condicionar a nuestra red, en
la que pues se ve una especie de boceto desenfocado, una imagen de bajo nivel, de
poco detalle de lo que sería la flor que queremos generar. Un poco con la idea de
que yo pueda tener una especie de lienzo donde yo pueda dibujar una flor un poco
mal hecha y gracias a nuestro sistema pues podemos generar una imagen más
realista de ello. Como ya saben el modelo Pix2Pix es lo
suficientemente versátil como para aplicarse a muchos problemas diferentes
con lo cual si no quieren trabajar con el dataset de flores que les facilito
aquí abajo pues pueden probar con cualquier otro tipo de dato diferente y
probar a solucionar un problema que a ustedes les pueda interesar más. Yo creo
que esa es una buena forma de aprender, el coger lo que yo les explico y adaptarlo
a vuestras necesidades, con lo cual pues lo único que les puedo desear ahora es
que disfruten de la programación dentro tutorial. Ah, por cierto, si valoras este
tipo de contenido formativo gratuito de Machine Learning en YouTube quizás
sería un buen momento para replantearte, hacerte Patreon de este canal y
apoyarlo financieramente. ¿Te has comido ya el spam? Perfecto, dentro tutorial. Lo
primero que quería comentar es que el tutorial que vamos a hacer hoy va a estar
basado en la librería TensorFlow y más específicamente en la versión 2.0. Si
todo esto de TensorFlow, Keras, Python y todas estas librerías que conforman el
ecosistema de Machine Learning te suenan pero no sabes qué te aporta cada una, te
recomiendo que te veas el vídeo que está saliendo ahora aquí arriba, que
básicamente fue un vídeo que preparé de cara a todos estos tutoriales en los que
vamos a empezar a utilizar estas herramientas y donde te explico un poco
qué te aporta cada una. En este caso, como ya he dicho, vamos a utilizar
TensorFlow la versión 2.0 que es una versión que todavía está en fase beta
y veremos un poquito también cuáles son los beneficios que te aporta
utilizar esta versión y por qué la vamos a seguir utilizando de cara al
futuro. En ese sentido, en la documentación de
TensorFlow y es importante mencionarlo, ya existe una implementación del modelo
PIX2PIX del cual vamos a hacer el tutorial hoy, de hecho el código que yo
le voy a explicar no es un código que yo haya implementado desde cero, sino que yo
he venido a la documentación, me he encontrado con el modelo y voy a adaptar
todo este tutorial a este código. Esto significa pues dos cosas, uno que el
código no es mío, yo lo he cogido y lo he adaptado un poco a las necesidades de
mi problema y un poco a este tutorial simplificando algunas cosas, pero casi
todo está conservado como el código original y lo segundo es que si de
repente tú estabas esperando por este tutorial para poder trabajar en el
proyecto de cara a la competición, pues que sepas que directamente aquí te
puedes venir y abrirte un Google Collab, un notebook y ya tienes el código ya
hecho, que no tienes que tampoco estar implementando lo de T0 si no es
tu caso. A ver, yo recomiendo que lo hagas porque vamos a aprender bastante,
pero siempre puedo decirlo, aquí lo aplican al dataset de las fachadas que
también es un problema muy interesante en el que tú planteas una segmentación
de una fachada y el sistema pues aprende a generarte fachadas realistas que
respeten más o menos la distribución que tú has especificado. Dicho esto, vamos a
comenzar. A ver, cuando vamos a afrontar este proyecto lo primero que tienes que
plantear es qué problema quieres resolver. Yo en mi caso, como ya he
explicado, voy a utilizar un dataset de flores y lo que vamos a buscar es
intentar resolver un problema en el que yo le paso una foto de entrada de una
flor muy especificada de manera muy borrosa, muy simplona, sin detalles, como
lo que podría ser el resultado de un dibujo mal hecho en Photoshop y la idea
es que el sistema aprenda a partir de esa imagen a generar una imagen un poco
más realista. Para eso pues lo primero que tenemos que hacer es generar ese
dataset. Yo en mi caso pues he buscado en internet, me he encontrado un dataset
muy grande de flores, en este caso son pues unas 8.000 imágenes de flores, como
pueden ver de diferentes tipos, colores, formas, con diferentes fondos, fondos
azules, fondos verdes y a partir de estas imágenes lo que he hecho ha sido
preprocesarlas para obtener lo que serían mis imágenes de entrada. En este
caso el proceso que yo he seguido para preprocesar estas imágenes pues ha sido
utilizar Photoshop porque es una herramienta con la que yo me siento
cómodo y básicamente he especificado una serie de efectos que quiero que se vaya
aplicando a cada una de las fotos. Para que conozca de Photoshop sabrá que hay
una herramienta que son las acciones que lo que te permite es automatizar una
serie de pasos que quieres que se aplique sobre una imagen, grabas todos esos pasos
y luego lo puedes aplicar en lote a una carpeta entera. Pues básicamente es lo
que he hecho, he aplicado efectos de desenfoque, de posterización que lo que
hace es eliminar detalles, desaturación de los verdes para que por ejemplo los
fondos me los convierta a negro, desenfoque gausiano y desenfoque de
superficies. Una serie de efectos que lo que hace es que mi dataset de flores,
estas que yo me he descargado y del cual tenéis el link en la descripción, pues
se conviertan en este tipo de flores. Básicamente pues esto sería el
input que nosotros le vamos a dar a nuestra red generadora para condicionar
el resultado que tiene que generar. Si todo va bien pues yo ya simplemente
generando una imagen tal que así o una imagen tal que así, pues puedo tener
como resultado una imagen realista de lo que sería una flor.
Con esto básicamente ya tendría generado mi dos carpetas, ignoren las que no
son ni esta ni esta, pero bueno con esto ya me he generado mi carpeta de
datos con las que voy a empezar a trabajar. Esta es una diferencia de cara
al código original y es que en el código original solamente utilizaban una
carpeta con las dos imágenes concatenadas en una sola imagen. Yo en mi
caso me lo genera en dos directorios diferentes por comodidad y el único
detalle es que en ambos directorios cada par de imágenes pues tienen que
conservar el mismo nombre. En este caso por ejemplo image 000000001.jpg
pues sería el equivalente a esta de aquí, sería la misma imagen y conserva el
mismo nombre, como se puede ver. ¿Qué hago con todo esto? Pues con todo
esto la idea ahora es empezar a trabajar en el código. Para programar lo que
vamos a utilizar es el entorno de programación de Google Collab, ya lo
hemos utilizado en otros tutoriales y hoy creo que es más importante que nunca
pues comentar un par de detalles. A ver, como ya saben Google Collab es esta
herramienta que nos ofrece Google, que nos permite crear cuadernos donde poder
programar código de Python y donde este código va a estar ejecutado no en
nuestro ordenador, no en el ordenador que estés utilizando, sino en
infraestructura que Google te deja gratis. Esta infraestructura y vamos a
hacerlo ya si venimos aquí en torno de ejecución, pues vamos a cambiar tipo de
entorno de ejecución. Podemos ver que podemos utilizar lo que sería una
máquina virtual normal con ejecución sobre CPU o podemos ejecutar nuestro
código sobre GPU o incluso sobre TPUs que son los tensor processor units.
Para este problema por ejemplo que vamos a estar trabajando con imágenes es muy
importante tener activada el procesamiento en GPU porque si no va a
ir muy muy lento. Como digo, esta infraestructura Google nos la ofrece
gratis y esto es muy cómodo porque nos va a permitir por ejemplo que cualquiera
pueda programarlo ya esté utilizando un súper ordenador de la NASA o si está en
el ordenador de la biblioteca del pueblo. ¿Por qué? Porque no estás trabajando
sobre tu ordenador estás trabajando en la nube. Esto está bien pero por ejemplo
para este tipo de problemas el que vamos a resolver hoy tiene unas cuantas
limitaciones y es que efectivamente Google para evitar que la gente abuse de
esta herramienta pues pone una serie de limitaciones técnicas que por ejemplo te
dicen que cada 12 horas la máquina virtual sobre la que estés trabajando se
va a reiniciar y se va a borrar todo lo datos que tengas con lo cual tienes que
buscarte un método en el que los datos se han guardado de manera
persistente fuera de esta máquina virtual y también cada 90 minutos de
inactividad también te va a desconectar. Con lo cual esto es un poco coñazo y mi
recomendación es que si tienes un ordenador medianamente potente no digo
nada avanzado sino algo una tarjeta gráfica, una 1080, algo que te permita
trabajar medianamente bien pues te recomiendo que
trabajes mejor en local porque el entrenamiento que vamos a hacer va a
requerir de unas cuantas horas y a veces es bastante coñazo que te estén echando
de la máquina virtual cada cierto tiempo y eso es básicamente mi consejo. Yo he
tenido bastantes problemas probándolo en Google Collab pero bueno, aún así el
tutorial lo quiero hacer en este entorno más que nada para que todo el mundo
pueda enfrentarse como digo independientemente del tipo de ordenador
que tengas. Dicho esto vamos a ver entonces cómo vamos a trabajar con
Google Collab y qué truco vamos a utilizar pues para trabajar de manera
más cómoda. Como digo, cada 12 horas nos van a borrar todos los datos que tengamos
en la máquina virtual así que tenemos que buscar algún método
cómodo que nos permita trabajar con nuestros datos porque imagínate que tú
subes todo tu dataset, va a tardar un rato, va a tardar mucho tiempo en subirse a la
nube y que cada 12 horas te lo vayan borrando. ¿Cómo sorteamos eso? Pues miren
el truco, el mejor consejo que les puedo dar es que el directorio de datos en
nuestro caso nuestra carpeta input flowers y target flowers, el input y
output del modelo generador, pues yo si se fijan lo tengo guardado en Google
Drive que es básicamente este sistema de almacenamiento de Google al estilo
Dropbox, con lo cual pues todo mi dato los tengo subidos en la nube, en la nube
de Google pero no es el Google Collab, son servicios completamente diferentes.
¿Qué pasa? Esto te va a tardar en subir un rato, pero una vez lo tengas subido
esto sé que no te lo van a borrar porque es tu Google Drive personal y lo
bueno es que aquí el Google Collab sabiendo que este es un método que se
utiliza frecuentemente pues te dan la opción de poder activar tu drive. Cuando
le das a este botón te sale aquí ya una celda de código que básicamente si la
ejecutamos, para ejecutar control enter, acuérdense, te dice vale vete a esta
url en el navegador, la abres, tienes que iniciar sesión con tu Google Drive y
entonces te van a dar un código que básicamente el código de
autentificación o de autorización que necesitas para que se conecte en ambos
servicios, lo pones aquí y entonces lo que está haciendo ahora es que dentro de
tu máquina virtual, dentro del ordenador que Google te cede, pues está creando un
directorio que se va a llamar Google Drive y que básicamente es una conexión
que se hace a tu servicio de Google Drive, con lo cual ya podrás acceder desde el
código que programes aquí, todos los datos que tengas almacenados en Google
Drive, si vienen aquí vamos a Drive, My Drive, dentro de esta carpeta tengo el
Flowers Data y aquí como ven tengo todas mis flores que creo que al abrir esto
ahora se va a trabar todo porque son demasiadas flores, con lo cual pues ya
tenemos todo listo para empezar a conectar y a cargar todos nuestros datos
a nuestro sistema. El primer paso una vez tenemos ya conectado el Google Drive
pues va a ser generar todo este código, todo el sistema que se va a encargar
de cargar los datos, preprocesarlos, convertirtelo a lotes y suministrarlo
pues a la arquitectura que vamos a diseñar posteriormente, entonces esto lo
vamos a hacer un poco rápido porque tampoco es tan importante de cara al
tutorial y vamos a empezar pues importando las librerías, tensorflow
que es la librería de Machine Learning, vamos a importar Matplotlib que es la
visualización de datos y que lo vamos a utilizar para visualizar las imágenes
e importamos Numpy que como ya saben es la librería de cabecera para trabajar
con matrices y vectores. Vale, después de esto vamos a definir en variables las
rutas donde está nuestro dato, para eso vamos a venir aquí a nuestro sistema de
archivos y con el botón secundario pues vamos a copiar la ruta donde está
alojada la carpeta raíz que contiene a input flowers y target flowers y ésta la
vamos a guardar pues en una variable como por ejemplo podría ser path, hay que
ponerla entre comillas porque si no nos va a dar problemas.
Vale, con esto tenemos la ruta, ruta raíz, luego podemos coger y también definir
pues la ruta donde tenemos los datos de entrada, ruta de datos de entrada que en
este caso sería pues vamos a llamarle impath y sería la ruta raíz más como se
llama la carpeta input flowers, esto por un lado, vamos a copiar ésta, vamos a
hacer otra también ruta de datos de salida que en este caso sería target
flowers y le vamos a llamar a esta ruta outpath y también otra donde vamos a
guardar, vamos a ir guardando los avances según vayamos entrenando a nuestra red
vamos a ir guardando una serie de checkpoints para que si en caso de que
el sistema falle pues podamos recuperar el estado de la red neuronal, entonces
ruta de los checkpoints, vamos a llamarle asempath checkpoints, ok, vale con esto
tendríamos ya los tres directorios que vamos a estar utilizando durante todo
este tutorial. Ahora lo que me interesa sería tener pues todo el listado de
archivos que están dentro de los directorios de datos, entonces para eso lo
que nos vamos a aprovechar es lo que nos permite hacer google colab que
básicamente ejecutar comando de consola, podemos generar aquí una variable que
va a ser imgurs y podemos ejecutar comandos como ls, entonces el resultado de
ejecutar este comando se va a guardar dentro de la variable imgurs, si hacemos
el list nos va a dar los datos en diferentes columnas con lo cual si le
pongo guión 1 nos lo va a devolver todo en una columna que es lo que nos interesa
y queremos que nos devuelva el listado de por ejemplo el directorio in path
para evaluar una variable que tenemos definido dentro de nuestro código como
esto va a ser un comando de bash pues lo tenemos que poner entre llaves, con esto
lo ejecutamos y si todo va bien dice que no puede
acceder, vamos a hacer un print y a ver qué ha pasado, en este caso está
detectando la ruta mal porque me ha faltado ponerle comillas cuando se
evalúe esta variable pues que esté contenida dentro de comillas, si lo ponemos
así ahora sí, ahora podemos ver como si hacemos un print, no se falta si es
la última línea, se va a visualizar, si vemos el contenido de esta variable pues
es una lista donde tenemos en cada uno de los apartados de la lista pues el
nombre del fichero de nuestro dato, perfecto. Vale el siguiente paso que
tenemos que hacer, ya lo he puesto y ahora voy a explicar el código pero
sobre todo es importante entenderlo porque recuerden que tal y como hemos
visto nuestro dato, si yo entrenara mi red neuronal con los datos tal cual los
acabo de cargar, pues si recuerdan cuando les he enseñado la carpeta con flores
todas estas flores estaban ordenadas por tipo de flor, color etc etc etc, es decir
que si yo le fuera suministrando la red neuronal estos datos y dijera mira la
primera parte va a ser una partición de entrenamiento y la siguiente va a ser de
prueba, pues como hay una ordenación lógica esa primera partición y esa
segunda partición van a ser diferentes tipos de datos, puede que la primera sea
una margarita y la siguiente sea una rosa, con lo cual de alguna manera nos
tenemos que cargar esta ordenación lógica y para eso lo que tenemos que
hacer es un shuffle, coger nuestros datos y reorganizarlos, yo en este
caso lo voy a hacer directamente reordenando, haciendo el random sobre los
nombres de ficheros que acabamos de cargar y es básicamente lo que hace esta
línea tecotivo de aquí, lo que ha hecho bueno ha sido coger y definir que voy a
trabajar con 500 imágenes, mi N en este caso va a ser 500 imágenes de flores y
mi dataset de entrenamiento va a ser un 80 por ciento, por eso cojo aquí y hago
la multiplicación, un 80 por ciento de mi N, es decir en este caso pues si son
500 van a ser 400 imágenes, las que usamos de entrenamiento, a partir de
ahí pues vamos a hacer el shuffle aleatorio que hemos mencionado y para
eso pues tenemos que coger, vamos a hacer una copia del listado de filenames que
hemos guardado en la variable imgurls y la guardamos en esta variable de aquí
randurls y después lo que vamos a hacer es un shuffle aleatorio aprovechándonos
de la función de la librería de nampi, nampi.random.shuffle y vamos a
hacer un shuffle de la variable que hemos generado, con lo cual aquí ahora sí ya
tendríamos los filenames reorganizados de manera aleatoria, esta línea de código
aquí he especificado un seed aleatorio que lo que va a hacer es que siempre que
lo ejecute nos genere la misma ordenación aleatoria y esto lo estoy
haciendo y lo voy a poner solamente de cara al tutorial, solo para el
tutorial, es decir esto es para que todos los que hagamos el tutorial pues cuando
pongamos esta seed aleatoria, cuando pongas que en este caso se reordene
según la identificación número 23, pues todos tengamos la misma reordenación y
todos veamos las mismas imágenes, pero de cara a trabajar normalmente pues tú
esto lo quitarías y así cada vez que lo ejecutes pues tendrías resultados
diferentes, entonces una vez tengo ya mi shuffle aleatorio pues lo que hago ahora
generarme dos particiones, una de entrenamiento y una de test, como está
hecho de manera aleatoria pues simplemente puedo decir oye cógeme de la
lista de urls aleatorias, acuérdense dos puntos y cógeme hasta el número de
datos de entrenamiento que he especificado, train barra baja n y los de test van a
ser coger el slice, la porción correspondiente a el final de los datos
de entrenamiento hasta el número total de datos que he especificado, que son
estos dos números que tengo aquí, si esto lo ejecutamos podemos ver que la
longitud de mi dataset es en total 7.725 imágenes, mi dataset de
entrenamiento tiene 400 imágenes y mi dataset de test pues tendrá 100
imágenes diferentes, esto no significa que hayamos cargado las
imágenes todavía, aquí lo único que tenemos son diferentes particiones de
los nombres de archivos de nuestros datos, ahora vamos a tener que hacer esto el
cargar las imágenes, básicamente lo que vamos a hacer ahora es que sabiendo que
la API que vamos a utilizar, la API Datasets de la librería de tensorflow, lo
que nos va a pedir es que cuando carguemos las imágenes, le especificamos
qué funciones queremos que se apliquen a la hora de preprocesarlas, por ejemplo
pues si quieres hacer data documentation, si quieres rescalar tu imagen, si la
quieres normalizar, todas estas funciones las tienes que implementar tú de cara a
poder decirle mira vas a aplicar esta función, esta función y esta función y
es lo que vamos a hacer ahora, vale, voy a ir un poco más ágil porque como digo
no es tan importante, entonces lo que voy a hacer es definir
pues la variable es que especifican cuál es el tamaño y la altura de mi
de mis imágenes y vamos a crear diferentes funciones que nos van a
servir bien, por ejemplo una de las funciones podría ser resize, ¿qué hace
resize? pues tú le vas a pasar la imagen de entrada, le vas a pasar la imagen de
salida, la target y le vas a pasar pues una altura y le vas a pasar un ancho, con
esto lo que va a hacer es coger ambas imágenes y vamos a hacer un tensorflow
no sé si esta es de esta librería, no, es dentro de image, dentro del módulo de
image pues habrá un resize que vamos a llamarlo y le vamos a decir mira me cogen
la imagen en image y le vas a hacer un resize de height y de width
hacemos esto para ésta y hacemos lo mismo para target, target image
target image, con esto ya estaríamos utilizando, definiendo nuestra
función para hacer resize llamando directamente a las funciones de tensorflow
devolvemos inimg y devolvemos target img, esto que acabamos de hacer con esta
función para rescalar las imágenes, vamos a ponerlo aquí, rescalar imágenes
vamos a hacer igual pues con el resto de funciones que nos hacen falta, por
ejemplo otra función pues podría ser normalizar, ¿qué es normalizar? pues si por
ejemplo nuestras imágenes vienen representadas como matrices de números
float que van de 0 a 255 pues en este caso la queremos trabajar con ellas en
el dominio de menos 1 a 1 ¿vale? porque esto va a hacer que la computación sea
más estable etc etc etc, entonces para pasar de 0 a 255 a
menos 1 1 ¿qué tenemos que hacer? pues si dividimos entre 127,5 que es la mitad de
255 nuestras imágenes pasan a estar en el dominio de 0 a 2 y si le restamos 1
pasan a estar en el dominio de menos 1 1, lo hacemos con ambas imágenes y listo, la
siguiente función si tiene un poco más de chicha y aquí me voy a parar un
poco y es básicamente una función que en el paper de pics2pics recomiendan
utilizar y que llaman random jitter y que básicamente cumple la necesidad de
hacer aumentación de datos ¿vale? recuerden que aumentar datos significa
coger tu dataset original y por ejemplo si tienes solamente una flor de una
margarita pues aplicar transformaciones aleatorias que perturben mínimamente
esa imagen para generar virtualmente más imágenes, por ejemplo si tienes la
margarita pues mover un poco a la derecha, un poco a la izquierda, un poco arriba
abajo y aleatoriamente pues generarte más imágenes de esa margarita que van a
darle mayor variabilidad a tu red neuronal y va a hacer que el entrenamiento
pues sea mejor, entonces en el paper especifican hacer lo que ellos llaman
random jitter que es como nerviosismo aleatorio y es básicamente esto ¿no? el
desplazar un poco la imagen ¿cómo lo implementan? pues ellos recomiendan que si
tu imagen es de 256 por 256 hagas una ampliación, la aumentes el tamaño a 286
286 y después con ese tamaño grande hagas un crop aleatorio de 256 por
256 es decir que cojas una zona de esa imagen más grande y te quedes con ella
con eso ya has conseguido hacer este desplazamiento aleatorio, además además
de hacer este desplazamiento aleatorio también te dicen que puedes hacer un
flip horizontal que es básicamente voltear tu imagen ¿vale? una margarita si
la volteas horizontalmente sigue siendo una margarita y ya está, entonces es
básicamente lo que hemos implementado en esta función de aquí, cogemos
nuestras dos imágenes y hacemos un resize aprovechamos ya la función que
hemos definido antes aquí arriba y hacemos un resize de 286 por 286 como
acabo de decir, a partir de ahí claro el desplazamiento aleatorio que tú
expliques sobre una imagen ha de ser igual en la otra imagen, con lo cual lo
que podemos hacer para hacer la misma transformación en ambas imágenes es
superponerla ¿vale? es como si estuvieras haciendo manualidades y dices vale pues
amplió las dos imágenes una sobre otra y voy a recortarla todos igual, de manera
igual ¿cómo lo haces? pues poniendo una encima de otra, esto lo conseguimos con
esta línea de código de aquí llamando a la función tensorflow stack donde le
pasamos las dos imágenes y básicamente con esto las apilamos, le decimos que no
las apile en el eje 0, con lo cual ahora vamos a tener si antes teníamos por
ejemplo una imagen de 256 por 256, ahora tenemos un objeto tridimensional que
serían 2 por 256 por 256. Esto lo estamos guardando en stack
images y ahora lo que hacemos es llamar a la función tensorflow del módulo
image random crop, esta es la que va a ser el recorte aleatorio. ¿Cuánto lo va a
recortar? pues al tamaño que la especificamos que va a ser 2, las dos
imágenes que tenemos a 256 por 256 por 3 canales de color que tenemos.
Estas dos imágenes las guardamos en la variable cropped image y las recuperamos
de nuevo, cogemos la primera y la segunda y ya está, la volvemos a
guardar en image image y tg image. A partir de ahí sólo nos queda aplicar el
random flip que lo vamos a hacer de manera aleatoria, lanzando una moneda al
aire, si sale el random flip llamamos aquí a tensorflow random uniform, le
pasamos los dos paréntesis para especificar que nos genera un escalar, un
número aleatorio y si ese número aleatorio es mayor a 0 con 5 pues
volteamos la imagen, si no la dejamos como está. Para voltear la imagen
llamamos a tensorflow image flip left to right, de derecha, bueno de izquierda a
derecha y lo hacemos con ambas imágenes inimage y tg image y con esto
devolvemos y ya tenemos hecho nuestra función que va a ser la aumentación de
datos. A partir de ahí pues sólo nos queda una función más y esta función
que nos falta es la que se va a encargar de cargar las imágenes
para cargar las imágenes pues lo que vamos a hacer es pasarle un filename, un
nombre de archivo y también vamos a especificar un flag, una variable
booleana que nos va a decir si queremos aplicar el data augmentation o no lo
vamos a querer aplicar, por defecto vamos a decir que siempre nos aplique el
data augmentation, que es lo que hacemos poniendo el igual true
esta función se implementa de la siguiente manera, parece que hay mucho
pero realmente no es nada, básicamente con esta línea de
código, bueno con esta línea de códigos con lo que estamos cargando la imagen
desde nuestro archivo, ahora sí estamos yendo al directorio de google drive y
estamos utilizando la función input del módulo input output read file para irnos
a nuestro directorio de input y el filename que hemos especificado y
cargando de ahí la imagen, esa imagen la decodificamos como una imagen jpg
utilizando el módulo de tensorflow y estamos haciendo un cast para
transformarla a valores de tipo float, a tipos decimales, ¿para qué? para que si
en caso por ejemplo cuando hacemos la normalización pues podamos hacer las
divisiones y nos haga las divisiones en formato decimal y no nos las
conviertan a números enteros y nos fastidie todo el sistema, aparte de eso al
final le ha añadido este corchete de aquí que lo que está diciendo es que
mira, las imágenes que me cargue me mantienes todas las dimensiones como
aparezcan, que van a ser 256 por 256, pero la última dimensión me la limitas a
que solamente tenga tres componentes, no más de tres, ¿por qué hago esto? bueno
esto lo estoy haciendo porque en algunos casos si a lo mejor se te va la patata y
cargas la imagen en formato png y esa imagen en formato png tiene un canal
alfa que van a ser cuatro componentes, esto posiblemente haga la lectura aun
cuando estamos utilizando la función decode jpg, pero no para cargar cuatro
dimensiones y eso si no lo contemplamos a posteriori pues nos va a generar
problemas, para ahorrarme ese problema digo mira me da igual que me vengas con
cuatro con cinco con veinte componentes, tú quédate con las tres primeras que
van a ser los canales rgb y ya está, hacemos esto para inimage, lo hacemos
también para reimage, no, vale me acabo de dar cuenta que tengo del código
anterior no cambiado la variable, espérate, listo, todas las variables que
eran reimage ya las he convertido a target image, es que tengo la chuleta
aquí y me he copiado el código y no había cambiado la variable, vale listo
entonces con esto ya tendríamos cargada en nuestras variables inimage y target
image nuestras imágenes, ahora qué hacemos? directamente utilizar las
funciones que hemos definido anteriormente para hacer el resize al 256
por 256, en caso de que hayamos activado la variable booleana pues aplicamos el
random jitter y normalizamos al dominio de menos 1 1, estos valores ya todo
procesado lo devolvemos, muy bien con esto ya tenemos la función que se va
a encargar de cargar las imágenes y de aplicar todo el preprocesamiento, qué
pasa? que nosotros vamos a utilizar el módulo como ya he dicho, la API de
dataset que ya viene implementada en tensorflow, que básicamente tú le
especificas una función de carga de datos y a partir de ahí él se va a
encargar de optimizarte todo ese proceso, esta función podría valernos la que
acabamos de generar aquí, como para decirle pues mira utiliza esta función
para carga de datos, pero qué pasa? que esta función es diferente ya sea si
estás entrenando o ya sea si estás haciendo el test, si estás con tu dataset
de prueba, por qué? porque en el dataset de prueba tú no vas a aplicar el data
documentation, tú no vas a decir quiero probar una imagen y quiero que además me
haga un flip horizontal, no no tú quieres que tu imagen te la conserve como
es, eso significa que tenemos que generar dos funciones diferentes ya sea de
entrenamiento y de prueba y eso es lo que vamos a hacer ahora, como ven lo que
he hecho aquí pues ha sido simplemente generarme dos funciones, loadTrainImage
y loadTestImage, donde le paso el filename ya no le paso la booleana y
estas funciones simplemente encapsulan la función de loadImage, una con la
variable data documentation en true y otra con la variable data documentation
en false, con lo cual ya tenemos esta distinción en los dos tipos de
funciones que vamos a utilizar para cargar imágenes. Podemos hacer una prueba
para ver si están funcionando bien como deberían, vamos por ejemplo a coger aquí
vamos a llamar a loadTrainImage, vamos a coger de nuestra variable randomURLs
la primera imagen y vamos a coger de la función matplotlive, imageShow y que
nos visualice esta imagen y vamos a ver si está funcionando, algún error, name
hate is not defined, esto es hate is not defined, vale porque me
equivoqué escribiéndolo aquí, esto debería de ser hate, vale, ejecutamos de
nuevo, otro error, este error tenía que
llegar y ha llegado ahora y hay que explicarlo porque detrás de esto hay
algo que es importante, vale, este error lo que nos está diciendo es que
aquí cuando hemos utilizado la función randomJitter, aquí si se fijan yo
he utilizado tensorflow random uniform para generar un número aleatorio, qué
pasa, podía haber por ejemplo utilizado la librería en ampi que también tiene
funcionalidades para generarte números aleatorios, pero en este caso he utilizado
tensorflow, lo que estoy diciendo es que esa operación va a venir incluida
dentro de nuestro grafo computacional en tensorflow y no va a ser un número que
se genere pues con la otra librería, qué sucede con este error, que cuando se
ejecuta esta función y la estoy ejecutando ahora, me está diciendo mira
es que no te puedo decir, no te lo puedo ejecutar porque este valor de aquí no
te voy a devolver un valor como tal, te estoy devolviendo un tensor porque
recuerden que en tensorflow hasta que tú no evaluas todo tu gráfico
computacional, lo único que se está moviendo por la estructura de datos son
tensores vacíos, es como promesas a futuro de que ahí va a haber un tensor
que te va a dar un número aleatorio, pero ahora mismo eso no está, vale, por qué
estoy explicando todo este rollo, porque esta es la forma de funcionar de tensorflow
versión 1.x, pero qué pasa, que esto cambia con la versión de tensorflow 2.0
en la versión de tensorflow 2.0 todo esto se simplifica, es una de las
grandes funcionalidades que añaden que es el famoso eager mode
que ya de hecho vendría en la versión 1.x pero que no está activado por
defecto, la versión 2.0 sí lo estaría, con lo cual para solucionar esto, estaba
previsto, lo que tenemos que hacer es activar la versión 2.0 de tensorflow
para eso pues vamos a coger, vamos a reiniciar todo el entorno de ejecución
vale, todo lo que teníamos hecho pues se va a reiniciar y lo único que tenemos
que hacer pues sería generar aquí una celda de código donde vamos a llamar a
tensorflow version y si se fijan aquí nos dice versiones disponibles 1.x o 2.x
pues si le damos aquí a 2.x no seleccionaría la versión tensorflow 2.x
con esto ahora sí podríamos volver a ejecutar esta celda, volveríamos a
cargar tensorflow en este caso ahora sí estaríamos cargando la versión 2.x y en
esta versión no nos debería dar problema porque aquí ya los tensores
no es que estén vacíos y estén esperando a que tú actives una sesión y
si no que se van evaluando en el momento y eso es comodísimo
esto es un poco de información para el que conozca cómo funciona tensorflow
pero no es indispensable si estás viendo por primera vez esta herramienta
vamos a ejecutar esto ahora y a ver qué pasa
vale tenemos un nuevo error pero ahora ya no es el que teníamos antes, con lo
cual cambiando la versión de tensorflow a la 2.0 ya lo habríamos solucionado
eso sería una forma de solucionarlo pero otra también podría haber sido utilizar
esto de aquí, esto es el decorador de funciones que lo que está haciendo en
este caso pues sería coger la función que acompaña y la estaría convirtiendo
directamente estaría llamando a la autograf, a la función nueva esta
también que tiene tensorflow y la estaría compilando la función en python
a una función del grafo computacional, esto es un mecanismo que ha encontrado la
gente de google para comunicar los dos mundos que hay dentro de tensorflow
ahora mismo que es la versión de la 1.x que teníamos este grafo computacional
súper optimizado y el eager mode que tenemos en la versión 2.0
cuando llamamos a tf.function pues se compila la función ya no estaría en
eager mode sino que ya estaría compilada a este a este grafo computacional y
sería más óptima de hecho ya lo veremos más adelante en este caso pues al
llamarla al llamar a esto aquí como estaría compilando toda esta función al
lenguaje de tensorflow al grafo computacional pues todas las
condicionales y todos los tensores que tenemos aquí también se traducirían y
tampoco notaría problema o sea que hubiera sido también otra forma de
solucionar esto. Si ejecutamos esto ahora pues vamos a ver cómo aquí abajo
aquí abajo vamos a tener otro error lo voy a parar ya porque la train image nos
está cargando dos imágenes y en mshows lo podemos visualizar una así que
vamos a poner aquí corchete para seleccionar la primera imagen y lo vamos
a ejecutar a ver si ya no nota problema vale y efectivamente ya no tenemos el
problema de antes pero si estamos teniendo una imagen en negro y esto es
porque nuestras imágenes si recuerdan las tenemos normalizadas de menos 1 a 1
y mshows si no recuerdo mal va de 0 a 1 con lo cual pues tenemos que coger esta
imagen de aquí y sumarle 1 y dividir entre 2 con esto de si le sumamos 1
sería de 0 a 2 y entre 2 de 0 a 1 me falta paréntesis por aquí
y con esto ahora debería estar bien la cosa efectivamente vale aquí estamos
viendo ya nuestra imagen de entrada esta sería la imagen número 1 y la imagen
número 2 sería la de salida que sería esta de aquí perfecto pues ya está vale
ya tenemos nuestras imágenes cargadas está pasando por el preprocesamiento de
datos de hecho podríamos ver si se está aplicando el random jitter éste
aleatoriamente debería de voltearse la imagen así que vamos a ver si llamando
la varia ps aquí vemos que el tallo está por aquí vamos a ejecutar la de
nuevo
sigue estando por ahí
sigue estando por ahí o coño sigue estando por ahí o está
fallando o no está funcionando la probabilidad vale ok ahora salía por
el otro lado vale es decir se están aplicando todos los preprocesamiento que
hemos definido en nuestras funciones perfecto con esto pues ya tendríamos
todas nuestras funciones creadas pero en ningún momento hemos cargado las
imágenes todavía para eso tenemos que generar los generadores que estábamos
mencionando antes y esto lo vamos a hacer a través de la api de data set
que viene implementada dentro de tensor flow para eso entonces pues vamos a
crear por ejemplo un train data set y lo vamos a hacer llamando al módulo data
creo que esto en pan mayúscula data sets vale y vamos a llamar una función que
se llama front es front tensor slices esto lo que va a hacer es decir vale yo
te voy a generar un data set a partir de un listado de elementos que tú me
especifique perfecto claro yo he definido mis funciones aquí arriba
nosotros vamos a llamar por ejemplo a lo a train image para generar nuestro
data set y como vemos aquí lo que te pide como entrada es un nombre de
archivo entonces que listado tenemos que pasarle aquí pues le vamos a pasar el
listado de nombre de archivo que ya tenemos especificado antes con lo cual
cuando esto cuando esto le pases el listado esto va a llamar a esta función
de aquí y esta función de aquí va a llamar a todas las anteriores para
generar el data set entonces vamos a especificar aquí tr urls como el tensor
a partir del cual queremos generar nuestro data set con esto ya le hemos
especificado que va a utilizar ese listado para generar el data set pero no
hemos cargado las imágenes como vamos a hacer eso pues vamos a vamos a llamar
aquí a la función map map es la que se van a encargar de hacer el mapeo entre
el listado de filenames y las funciones que tenemos especificadas aquí arriba
en este caso pues queremos que nos mape cada uno de los filenames que tenemos
aquí con la función train image y con esto pues ya debería estar funcionando
la cosa bien además aquí hay una propiedad que
podemos especificarla que es el número de procesamientos en paralelo que
queremos que tenga estas funciones cuando vayan siendo llamadas entonces lo
normal sería utilizar tantos hilos en paralelo como cpus tenga tu ordenador
pero también podemos dejar que sea tensor flow el que decida cómo ajustar
este parámetro vamos a hacerlo así esto debería funcionar perfecto y y en
principio ya estaría vale ya con esto habríamos generado un data set donde
podemos ir obteniendo diferentes imágenes pero nos faltaría una cosita
más y es una funcionalidad muy cómoda que tiene esta este objeto el objeto data
set que básicamente que tú le puedes especificar que te distribuya los datos
en diferentes lotes porque como ya saben nosotros podemos especificar a la hora
de entrenar a nuestra red neuronal diferentes tamaños de lotes que van a
ser los que les suministremos para aplicar todo el proceso de optimización
tú aquí pues sí por ejemplo quieres un batch size de 32 pues le puedes decir
train data set agrúpame todo los datos que has cargado en el lote de 32
tamaños como en el paper especifican que ellos utilizan un tamaño de lote de 1
pues es lo que vamos a hacer con eso ya tendríamos el objeto creado data set
que ya vamos a poder utilizar de manera cómoda pues por ejemplo le podemos
decir oye del data set dame 5 lotes de tamaño 1 pues llamas a la función take
y te devolverá el objeto que te va a suministrar eso o por ejemplo lo puedes
iterar y dices oye mira para in y mg y teje mg que van a ser los los dos
objetos que te devuelve cada uno de los lotes pues itérame el train data set
para los cinco valores vale esto lo hacemos y si me viniera que arriba
podría copiar esta línea para visualizar imágenes la metemos dentro
del bucle for
y ahora en vez de hacerlo a train image pues directamente podemos coger
por ejemplo teje mg le damos a enter y esto debería de fallar porque teje mg
que shape tiene
vale porque tiene claro porque lo que me está devolviendo en cada objeto es el
lote entonces le tengo que quitar esta dimensión de aquí que es la que me
molesta para eso pongo que la dimensión elijo el primer elemento el resto lo
dejamos igual le damos de nuevo ahora no da problemas y efectivamente pues te
visualiza cada una de las imágenes como no bueno sólo ha visualizado uno pero
porque no puesto show para que me genera diferentes plots aquí vamos viendo cómo
va iterando y ahora si nos va cargando las diferentes imágenes ok de esta
manera es como hemos hecho todo el proceso de carga de datos es un poco
coñazo hay que decirlo hay que hacer muchas cosas pero a partir de ahora ya
todo el tratamiento de datos va a ser mucho más cómodo esto que hemos hecho
aquí pues tenemos que hacerlo de manera similar con nuestro test vale la palabra
test y vamos a ir reemplazando aquí aquí aquí aquí y aquí
vale con esto ya tendríamos creado los dos generadores que nos harían falta
para trabajar todo lo siguiente esta es la parte como digo de carga de datos lo
más para mi gusto coñazo junto a toda la parte del bucle de entrenamiento
pero bueno con esto ya tendríamos el primer paso completado lo siguiente que
vendría ahora sería empezar a diseñar nuestro sistema pics to pics que
básicamente para lo que estamos aquí
coffee time a ver lo que viene ahora no es tan complicado y sin embargo es la
parte más importante de todo el sistema que vamos a generar porque es el diseño
de la arquitectura pics to pics no el modelo este que vimos en el vídeo el
modelo pics to pics si se acuerdan es un modelo generativo una red generativa
adversaria condicionada es decir que no nosotros vamos a condicionar lo que se
va a generar en función de un input que le vamos a suministrar en este caso
imágenes y que en la parte del modelo generador pues viene implementado por una
red de tipo unit que ya saben que es un tipo de red donde tenemos una parte
convolucional donde se va comprimiendo la información una parte de
convolucional donde se va descomprimiendo y una serie de skip
connections que conectan cada capa en el mismo nivel
por el contrario la parte del discriminador no la mencionamos en el
vídeo anterior pero sí te puedo adelantar que es un tipo de red también
convolucional con una serie de características y que le da el nombre de
patch cam vale ahora veremos exactamente cómo se implementa no es tan complicado
es lo bueno estar utilizando tensorflow que nos va a facilitar bastante el
trabajo en este sentido además no sólo eso sino que en el propio paper todo
viene bastante bien explicado no suele ser tan habitual que den este nivel de
detalle pero si se finaliza en el paper hay un apéndice donde te explican cómo
la red generadora y la red discriminadora vienen implementadas vale en este caso
pues te dicen mira la red generadora tiene un encoder donde viene implementado
y te ponen este código de aquí que anteriormente te han explicado ahora
vamos a echarle un vistazo a esto con más profundidad pero sepan que toda la
información de lo que vamos a implementar ya viene detallado en el
paper por lo tanto lo primero que vamos a hacer va a ser importar dentro del
módulo de Keras todas las capas que nos van a servir pues para facilitarnos un
poco el trabajo y vamos a empezar a diseñar nuestro generador
si miramos al paper podemos ver cómo nos explica que a ver cada vez que en el
código que nos ponen aquí abajo salga una C y una K esto lo que denota es un
bloque donde hay tres capas diferentes por un lado y una capa convolucional que
va a analizar la imagen luego va a ver una capa de batch normalization que va
a ser el proceso de normalizar a nivel de lote y luego va a ver una capa de
activación donde hay una Renu, en este caso la K lo que viene a denotar es
el parámetro de número de filtros que va a tener la capa de convolución con
eso pues podemos decir ah vale si me estás diciendo esto entonces el encoder
ya saben la parte donde la imagen se va a ir comprimiendo capa tras capa pues va
a estar formada por una sucesión de estos bloques que nos especifican aquí
convolución batch norm y relu pues aquí nos dice oye va a venir uno que tiene 64
filtros luego otro con 128 otro con 256 512 12 hasta llegar a este punto esto va
a ser el embudo el cuello de botella de nuestra red neuronal cuando lleguemos a
este bloque de aquí además nos especifica que
bueno que después de la última capa del decoder se aplica una convolución vale
esto lo veremos ahora y que todas las capas del encoder son leaky relu vale no
es exactamente una capa relu normal de las que hemos visto en el canal sino es
una capa leaky relu que es casi lo mismo que una relu lo único que la parte de
negativa la derivada no es cero vale es una derivada con una ligera pendiente
entonces sabiendo que estos bloques se van a estar repitiendo todo el rato que
vamos a tener bloques de tres capas bloques de tres capas tal tal tal lo que
podemos empezar a hacer es especificar ese bloque vale yo creo que algo muy
importante a la hora de trabajar al diseñar redes neuronales y pasa también
en muchos otros ámbitos de la programación es trabajar de forma
modular y de forma ordenada entonces todo lo que pueda ser encapsulado dentro
de una función dentro de un bloque lo mejor es hacerlo así para que después
podamos reutilizarlo de manera cómoda entonces vale sabemos que tenemos que
hacer una capa convolucional batch norm y relu pues entonces nos venimos para acá
y vamos a especificar como si fuera una función también un bloque que va
a representar a esas tres capas en este caso sabemos que en el encoder lo que
está pasando sobre la imagen es que se está realizando una compresión de la
imagen estamos haciendo un down sampling pues
podemos llamar nuestra función sample y ahora vamos a ver qué parámetros le
vamos a meter a priori ya sabemos que uno de los
parámetros va a ser el número de filtros de la capa convolucional ya
podemos especificar aquí filters entonces ahora pues ya podríamos
empezar a diseñar nuestro bloque neuronal por así llamarlo de la manera
la que queras nos lo facilita esto sería pues vamos a crear a nuestro
modelo el objeto sequential vale sequential es la forma en la que la
especificamos a que era que lo que vamos a definir ahora es una secuencia de
capas la primera capa pues ya hemos visto que sería
le añadimos una capa convolucional para las imágenes una capa convolucional con
dos dimensiones y de momento lo único que le vamos a especificar es el número
de filtros que tiene ok entonces esta sería nuestra capa
convolucional después de esto recordamos que lo que
venía era una capa de batch normalization vale
pues simplemente nos venimos para acá y decimos result batch normalization
y listo y ya no los estaría añadiendo directamente a nuestro bloque y lo
último que tendríamos que añadir también lo hemos visto que es una capa
de activación que en este caso lo que sería es una liquirrelu
ahí estaría pues con esto tan sencillo como esto ahora podríamos escoger aquí
y decir oye el resultado devuélveme el resultado y
entonces pues ahora ya podríamos coger y llamar a esta función
y cada vez que ejecutemos esto pues nos estaría creando un mini bloque
donde nosotros podríamos ir pasándolo los datos y haría la convolución el
batch normalization y la liquirrelu perfecto ahora hay una serie de detalles
que especifica el paper que tenemos que ocuparnos por ejemplo nos dice todas las
convoluciones son de filtros espaciales de tamaño 4x4 hay que especificarle ese
tamaño cada una de estas tiene un stride es decir cuántos píxeles se va a mover
el filtro sobre la imagen de 2 pues también hay que especificarlo luego por
aquí abajo creo que especifica que como una excepción a la notación que tenemos
arriba la que nos explican aquí pues la capa de batch normalization no se
aplica a la primera capa de 64 del encoder esta de aquí no tiene batch
normalization pues también nos tenemos que ocupar de esto
y creo que son estos los pocos detalles que hay que especificar y también este
vale que los pesos nuestros parámetros cuando están creados a la hora de
inicializar lo vienen inicializados por un ruido gausiano de media cero y una
depiación estándar de 0,02 entonces todos estos atributos lo vamos a
especificar dentro de nuestro bloque para pues a pegarnos un poco a lo que el
paper nos está contando no vaya a ser que sea importante entonces lo primero
que vamos a definir pues va a ser un inicializer una variable inicializer
donde vamos a meter el ruido aleatorio gausiano normal pues que acabamos de ver
que hay que poner la media tiene que ser 0 y en la depiación estándar si no
recuerdo mal 0,02 con esto ahora pues nos podemos venir a
nuestro filtro convolucional y podemos decirle que nuestros filtros se van a
inicializar kernel initializer con el inicializador que hemos especificado
arriba además aquí en la capa convolucional sabemos que tenemos que
especificar que el tamaño de los filtros va a ser de 4 por 4 que los
strides van a ser de tamaño 2 también podemos decirle que el padding sea de
tamaño igual para conservar el tamaño de las de las de los mapas de
características según van pasando por las capas y
y poco más por último hay un detalle que podemos añadir y es que cuando tú
estás utilizando batch normalization esta computación va a añadir además de hacerte
una normalización va a añadir una serie de parámetros que son equivalentes a
tener un parámetro de sesgo en la capa anterior vale esto es un detalle muy
pequeño pero puesto a ahorrar los parámetros lo que podemos decir aquí es
oye just bias creo que se llama el si efectivamente just bias es para decirle
mételo un parámetro de sesgo o no se lo meta pues en este caso le vamos a decir
oye añadele parámetro de sesgo siempre que no estemos aplicando batch normal
normalización porque si lo estamos aplicando realmente esto no nos hace
falta con esto ya tendríamos hecho todo nuestro bloque
nuestro bloque de downsampling vale el que va a ir por la parte del encoder si
lo ejecutamos veremos que nos da un error porque no hemos especificado el
parámetro filtro vale exacto aquí habría que poner esto así y de momento
parece que secuencial is not defined secuencial is not defined secuencial
diría que está bien escrito no sé si falta algo más de la librería de
queras vamos a
vale perfecto ahora sí pues eso con esto ya tendríamos diseñado una función
que nos va a generar todos estos bloques que no van a hacer falta de cara a
diseñar nuestro encoder y claro esto está muy bien para diseñar a nuestro
encoder y también para otra arquitectura que vamos a hacer más
adelante pero todavía nos faltaría otro bloque más que se ocupe de la parte del
decoder no la que va realizando el app sampling de la imagen para recuperar y
generar la imagen final si nos venimos al paper vemos que el decoder viene
especificado por unas capas que en este caso las codifican como sede y que aquí
arriba te dice que que básicamente es un bloque donde se está produciendo una
convolución un batch normalización un dropout y una capa de activación relu
vale y también nos indica aquí que las convoluciones en el encoder por supuesto
hacen un down sampling de factor 2 es decir que cada vez que se da un paso se
reduce el tamaño de la imagen en la mitad y esto lo hace esto lo hacemos
gracias a tener el parámetro strides a 2 al mismo tiempo que en el decoder se va
realizando un app sampling de factor 2 y esta es la principal diferencia entre el
bloque de down sampling y el bloque de app sampling entonces por eso mismo lo
que vamos a hacer es venir para acá y nos vamos a copiar toda esta función
entera vamos a crear una nueva celda y vamos a crear aquí ahora nuestro bloque
app sampling con la única diferencia de que en este caso pues
lo que vamos a controlar si lo queremos aplicar o no es el dropout porque en
algunas ocasiones si nos interesa y en otras ocasiones no nos interesa como
podemos ver, cuando lo tengamos activado estaremos haciendo este
bloque de aquí cuando lo tengamos desactivado estaremos haciendo estas
partes de aquí las que no tienen la D, entonces app sampling pues tiene que ser
lo mismo una capa secuencial inicializador en este caso tenemos una
capa convolucional pero es una capa convolucional inversa y esto se hace en
Keras con la capa convolucional 2d transpose vale tabulamos para que no me
de el toc y que se quede toda la misma altura el resto de parámetros son
iguales filtros especificados por parámetro padding the same y el utilizar
el bias esto lo podemos quitar porque en este caso siempre vamos a hacer un batch
normalization perdón lo que podemos quitar es esta parte de aquí esto lo
podemos poner a falso para que nunca se utilice el parámetro de sesgo y a
partir de ahí pues podemos sacar la capa de batch normalization porque sabemos
que la vamos a utilizar siempre
ok y en este caso pues lo que vamos a utilizar es el parámetro que hemos
especificado arriba de apply dropout que todavía no lo pilla porque no está
ejecutada la celda así que lo escribimos dropout en el caso de que se
tenga que aplicar dropout pues que hacemos aplicar dropout añadimos una
capa que es una capa de dropout con 0,5 creo que era
con esto pues si no me equivoco ya tendríamos todo lo que hace falta
capa de dropout la capa de dropout es una capa que simplemente lo que hace es
desconectarte una serie de conexiones de manera aleatoria y que sirve como
como elemento regularizador de la red ok pues con esto ya tendríamos diseñado
nuestro bloque absento y si lo probamos pues tampoco debería darnos mucho
problema correcto vale estaba mirando en el paper y me acabo de dar cuenta que
especifican que el decodificador la activación no es una leaky relu sino
que simplemente es una capa relu así que lo que vamos a hacer es cambiar esto
aquí por esto de aquí así es sencillo es hacer cambio dentro de nuestra red
neuronal si se dan cuenta muy bien pues ahora que tenemos los bloques creados
solamente tenemos los ladrillos de nuestra arquitectura y ahora lo que
tenemos que hacer es empezar a conectarlos para diseñar cosas más
grandes si recuerdan nuestro modelo generador era una capa de tipo unit
antes explicado lo que era esto con lo cual pues nos tenemos que preocupar de
diseñarla ya verán que no es tan complicado si se fijan lo que estamos
haciendo aquí es definir una función generator vale la función que va a ser
de generador en nuestra arquitectura y en este caso pues lo primero que se hace
es especificarle una capa de entrada es como decirle mira a partir de este punto
en este punto te vamos a meter una serie de datos que van a tener la siguiente
dimensión estas dimensiones son non no le decimos cuáles otra que no le decimos
cuáles y otra que le decimos que son tres porque hacemos esto porque estamos
dejando abierto a que nuestra imagen pueda tener diferentes tamaños con lo
cual esto corresponde correspondería con la el ancho de la imagen la altura de la
imagen y los canales de color si sabemos que son tres canales de color rgb a
partir de ahí lo que estamos creando es una lista donde estamos declarando cada
uno de los bloques que van a conformar a nuestra red de nuevo no estamos
conectando nada todavía simplemente estamos diciendo oye vamos a hacer un
conjunto de bloque donde va a haber una capa de downsample con 64 filtros y
donde nos aplique el batch normalización después una de 128 después
256 luego 512 512 512 512 512 de donde salen estos números de nuevo del paper
si no venimos aquí simplemente hemos declarado los bloques que nos especifica
el paper y lo mismo estamos haciendo con el decoder ok 512 512 512 y luego a
partir de aquí tienen que ser capas que hagan el up sampling pero que no tengan
activado el dropout pues de nuevo nos venimos para acá y le decimos 512 512
512 aplícame el dropout y a partir de aquí no me lo apliques vale estoy
viendo que en este caso en el código yo tenía definido por defecto que esto
estuviera a false vale ahora tiene más sentido vale una cosa interesante hacer
siempre que estén trabajando con capas y sobre todo si están trabajando con
imágenes es mantener mediante comentarios o de alguna manera un
control sobre las dimensiones de nuestras imágenes porque siempre te va a
permitir tener una idea más clara de lo que estás haciendo ok por ejemplo en
este caso si yo le meto una imagen estamos metiendo vamos a pensar un lote
de 50 imágenes nuestro batch size es 50 pues qué dimensiones vamos a tener de
entrada vamos a tener batch size 256 256 por tres canales de color cuando lo
pasamos por la capa down sample sabemos que estas imágenes están reduciendo a
la mitad de tamaño con lo cual las dimensiones ahora van a ser
anotaríamos batch size de nuevo y la mitad 256 128 128 y como estamos
generando 64 mapas de características porque tenemos 64 filtros pues ahora la
dimensión la cuarta dimensión va a ser 64 filtros si esto lo vamos repitiendo
pues se divide se divide se divide se divide se divide y el número de
filtro de canales se corresponde con el número de filtros que tenemos y de la
misma manera pues haríamos lo mismo con el decodificador donde veríamos cómo la
imagen poco a poco después de llegar de la parte del codificador pues iría
ampliando ampliando ampliando y al final tendríamos una imagen de 128 128 y 128
filtros faltaría todavía añadir una última capa una capa más que te genere
la imagen que está buscando por lo tanto vamos a empezar con creando la última
capa vale esta última capa como ya hemos dicho tiene que ser la que se
encargue el resultado de esta última capa tiene que ser la imagen que estamos
buscando por lo cual si lo pensamos pues esto debería ser algo así como una capa
convolucional de dos dimensiones tras puesta de ser una capa que también va a
ser una sampling porque venimos de una imagen de 128 por 128 y en este caso el
número de filtros que debería tener esta capa es lo que define el número de
canales que tiene el resultado de lo que sale y si queremos generar una imagen
tiene que tener tres canales que son los tres canales de color con lo cual tiene
sentido decirle que el número de filtros que tiene que crear son tres
filtros vale el kernel size vamos a especificar de nuevo
kernel size de 4 vale que eso no varía lo especificaba el paper un strike de 2
para que suba para que no tuplique el tamaño anterior que era 128 por 128
padding podemos ponerle nuevo el same
inicializador vale no hemos creado el inicializador vamos a crear un
inicializer de nuevo para que escribir si puedes copiar vale perfecto entonces
con esto vamos a poner el kernel inicializer
seguro que hay algunos por algún lado inicializer vale y directamente podríamos
ponerlo como una capa aparte pero vamos a ponerlo aquí vamos a especificarle el
tipo de capa de activación de esta capa y el tipo de activación si lo piensan
nuestras imágenes vienen en el rango de menos 1 a 1 con lo cual tendría sentido
en este caso hacer lo mismo y por tanto especificar una capa de
activación de una tangente hiperbólica que es la que tiene el dominio de
salidas con una sigmoide pero en vez de 0 a 1 va de menos 1 a 1 ok con esto
técnicamente ya tendríamos el último bloque de todo nuestro sistema y que si
generaría una imagen pero de nuevo no hemos conectado nada para conectarlo lo
que podemos hacer es un bucle for que vaya iterando pues por cada una de las
capas que tenemos especificado en los dos stacks por ejemplo podemos coger
decir for down in down stack y ahora si se fijan pues en cada
iteración la variable down va a ir tomando cada una de las capas que
tenemos en nuestro stack con lo que podríamos coger por ejemplo y decir
mira vamos a guardar en x el resultado de coger nuestra capa down la que se vaya
asignando y pasarle el resultado de la iteración anterior que como la estamos
guardando en x pues la anterior también va a ser x con lo cual si esto tú lo
repites muchas muchas veces estás cogiendo el resultado de pasar el
procesamiento por una capa coges en la salida y se lo pasas a la siguiente coges
la salida se lo pasa la siguiente etc etc etc hasta haber pasado todas las
capas del down stack alguien dirá pero carlos y cuál es la primera entrada que
le vamos a meter pues efectivamente antes del bucle for habría que decirle
que la x es igual a inputs que lo tenemos especificado aquí arriba con
esto lo que hemos hecho solamente con estas dos líneas con este bucle for ha
sido conectar cada una de las capas del decodificador tan sencillo como eso
o sea es gracias a que eras y tensorflow al igual que con herramientas como
pytorch es súper sencillo diseñar redes neuronales
vale qué pasa ahora si queremos hacer el decodificador pues tan sencillo como
coger y decir oye para app in app stack
en app stack pues simplemente me vas cogiendo y me va guardando de nuevo en
x el resultado de pasarle la x a cada una de las capas coges el resultado lo
guardas coges el resultado lo guardas etcétera cuando hemos terminado estos
dos bucles ya tendríamos el decoder y el encoder todas las capas conectadas y lo
último que nos faltaría sería pues pasarlo por la última capa que como ya
sabes la hemos definido como last ok nosotros fácilmente podríamos hacer
esto así y lo que habríamos hecho ahora mismo nuestro generador sería una
arquitectura de tipo hourglass no estas que parecen un reloj de arena porque
vamos codificando y decodificando o sea que ahora mismo ya hemos creado un
generador que podría funcionar en cierta manera para resolver unos cuantos
problemas que pasa que como mencionamos en el vídeo esto para ciertos problemas
como lo que es generar una imagen puede ser muy agresivo el hecho de que tengamos
que pasar toda la información por un cuello de botella de tamaño 1 por
ejemplo para solucionar eso pues ya saben que lo que se puede utilizar es las
skip connections vale que son básicamente conexiones que se van a
saltar pues el procesamiento de determinadas capas y que si por ejemplo
tenemos una arquitectura en la que todos los niveles del decoder y del encoder
están conectados según el nivel al que correspondan pues lo que tenemos es una
arquitectura de tipo unit que es la que queremos implementar en este caso
entonces vamos a ver cómo lo podemos hacer de manera sencilla
cuando tenemos una skip connection en la unit lo que cogemos es una capa de el
encoder y la concatenamos al mismo nivel del decoder por lo cual lo que tenemos
que hacer aquí es de alguna manera según estamos bajando por la fase del
encoder y guardar todos los resultados que vamos teniendo en esas capas y
después concatenarlos al mismo nivel que tengamos en el decoder eso lo podemos
hacer de manera sencilla pues nos creamos una variable s que representa
las skip connections y va a ser una lista donde vamos a ir añadiendo según
vamos bajando por el encoder los diferentes elementos vale podemos coger
decir añádeme el resultado de de haberlo procesado en esta capa eso ya lo
tendríamos en ese ahora cuando hemos terminado de pasar por el encoder lo que
tenemos que hacer es empezar a conectarlos con el decoder y el orden va
a ser el mismo el la concatenación que tenemos que hacer de estas señales va
a ser la misma pero invertida es decir la última capa que hemos metido en
nuestra lista ha de ser la primera que tenemos que concatenar en él en el
decoder por eso tiene sentido que cojamos aquí y la lista de las skip
connections de los resultados la invirtamos además en este caso el
último elemento que hemos guardado que es el del cuello de botella no nos hace
falta con lo cual lo podemos dejar ir vale podemos cogerlos hasta el penúltimo
elemento y ya con eso pues podríamos empezar a concatenar aquí el resultado
antes de pasarlo por nuestra capa de sampling para concatenar el resultado de
dos capas pues efectivamente que era se nos facilita una función que se llama
concat de la cual nos podemos aprovechar para hacer esta concatenación entonces
nosotros nos podríamos venir ahora aquí hay que ponerlo exactamente después
para saltarnos así el cuello de botella la primera vez entonces podemos decir
oye x ahora va a ser el resultado de llamar a concat con el resultado de
dx y la skip conección correspondiente en este caso como estamos iterando por
objeto y no por índice lo que podemos hacer es aquí hacer una cremallera me
gusta pensar que estos cremalleros a que una de dos listas con la lista de las
skip connections con lo cual ahora esto no te vuelve el app y no te vuelve el
skip vamos a concatenar ambos elementos ok con lo cual
según entramos al encoder perdón al decoder lo primero que hacemos es el
app sampling del cuello de botella concatenamos el resultado con la
primera skip connection y eso va a ser el input de la siguiente iteración el
resultado concatenamos etc etc etc automágicamente lo que hemos hecho ha
sido diseñar una unit ok esto por sí solo podría haber sido un
tutorial perfecto para para el canal porque estas arquitecturas las units no
solo sirven como elementos generador dentro de una red generadora sino que
son bastante útiles como por ejemplo en problemas de segmentación ok si tú
tienes una imagen y quieres segmentar por ejemplo el caso típico una célula que
te diga esto de aquí es una célula cancerígena pues utilizar units son son
buenas herramientas para este tipo de problemas ok y ya ven qué sencillo es
diseñar un bicho de estos en este caso para completar el generador yo aquí me
calentado un poco y he puesto return last pero como estamos utilizando el
modo funcional de Keras lo que tenemos que hacer es
vamos a guardar esto aquí y lo que tenemos que hacer entonces es devolver
un modelo donde le vamos a especificar que los inputs son
el input va a ser la variable inputs que tenemos arriba especificada y el output
de nuestro modelo viene definido por la última capa que tenemos aquí esto lo
que va a hacer es construir un modelo utilizando todas estas conexiones y va
a ser el objeto que ya nosotros podremos utilizar como un bloque entero para para
hacer un modelo generador en este caso para comprobar que el generador funciona
pues lo que podemos hacer es instanciarnos un generador de los que
acabamos de crear vamos a ejecutarlo a ver si se queja
vale se queja del append, append, append, append
vale porque la función append no te devuelve nada directamente te añade el
objeto por lo cual esa línea estaba mal y ahora no debe, vale se queja keywords
argument not understood inputs estamos aquí inputs inputs vale va a ser aquí
outputs vale lo ejecutamos y parece que funciona vale como sabemos que funciona
lo que podemos hacer me voy a copiar una línea de código de aquí para ir un
poco más rápido vamos a llamar a nuestro generador al que hagamos de
instanciar y le vamos a pasar una imagen para ver cómo la interpreta
ok entonces en este caso pues vemos que como está el generador instanciado
aleatoriamente está no ha sido entrenado todavía pues lo que nos genera es
basura pero no se ha quejado y el resultado es que nos ha generado una
imagen de 256 por 256 con tres canales de color que interpretado como una
imagen es decir nuestro generador parece que de momento está bien
cuando tenemos el generador creado pues ya tenemos uno de los elementos
importantes implementado dentro de nuestra red generativa adversaria pero
faltaría la otra parte que cuál es el discriminador la tarea del discriminador
pues tiene que ser la de observar el resultado del generador y decir si el
resultado lo que está generando es cierto o es falso respecto a lo que él
considera que debería ser una imagen correcta que pasa en este paper en vez
de utilizar un generado un discriminador tradicional como podría
ser una red convolucional que te lleva un resultado final a un escalar que te
indique si esta imagen es cierta o esta imagen es falsa lo que utilizan es un
tipo de red denominada patch can vale la patch gan es lo mismo es la misma idea
de lo que acabo de explicar una red convolucional pero con una única
diferencia y es que en vez de llegar a un resultado final de un único valor lo
que te va a devolver es una cuadrícula donde se van a evaluar diferentes
porciones diferentes parches de ahí su nombre de la imagen original con lo
cual a lo mejor en vez de obtener un único número final lo que está
recibiendo es una cuadrícula en la que te dice mira la esquina de arriba el
trozo de la esquina de arriba parece que es verdadero el siguiente trozo
superponiéndose parece un poco falso este de aquí parece un poco cierto este
parece un poco falso con eso lo que se está consiguiendo es crear un sistema
donde no nos preocupamos de si toda la imagen parece cierta pero simplemente
nos fijamos en que ciertas regiones tengan que ser realistas entiende más
o menos esta idea entonces cómo se implementa una patch gan pues tenemos
dos opciones una pensarlo o dos leernos el paper si nos venimos al paper vemos
que nos lo explican aquí y esta codificación ya la conocemos esto es lo
mismo que teníamos antes son los mismos bloques y lo único que no viene a
decir es que un discriminador aquí viene implementado con los bloques de
convolución batch norm y relo y además aquí nos indicaba además que las
convoluciones en el discriminador son también da un sample da un sampling de
factor 2 con lo cual esta descripción de aquí no deja de ser una red
neuronal convolucional pero no en la que no vamos a acabar con un valor final
sino que simplemente pues vamos a cortarla en un punto y vamos a decir
vale pues que nos queda resultados aquí una imagen de 30 por 30 pues son 30
regiones las que estamos evaluando se entiende la idea perfecto pues vamos a
implementarlo por tanto para hacernos el discriminador vamos a hacernos de nuevo
una función como hemos hecho antes le llamamos discriminator y en este caso si
se dan cuenta no vamos a tener solamente un único input sino que vamos a tener
dos porque porque el discriminador su tarea va a ser la de coger la imagen
generada por el generador observarla y decir oye esta imagen me parece real o
no me parece real pero qué pasa que en este caso el discriminador al igual que
pasa con el generador está condicionado vale es un discriminador
condicional y eso significa que el discriminador también tiene acceso a la
imagen original que estamos metiéndole al generador al garabato de la flor así
que básicamente su tarea sería algo así como yo te voy a decir si esta imagen es
cierta no es cierta la que ha generado el generador en función del input que
tú me has pasado vale decir esta imagen que ha generado sí parece que se
corresponde o que no parece que se corresponde al input que me has pasado
eso significa que aquí lo que tenemos que definir pues son en vez de un input
dos inputs vamos a empezar con el primero le especificamos una shape de
non y tres canales de color y le vamos a poner un nombre para identificarla que
sea input image vale esta línea la podemos duplicar y ahora podemos poner
que la otra es la del la del generador y a éste le vamos a llamar
ok qué pasa que ahora tenemos dos imágenes claro esto cuando sea
procesado por el gen por el discriminador de alguna manera tiene que
estar combinadas estas dos imágenes con lo cual podemos hacer lo que ya hicimos
antes que era decir o yo voy a coger esta imagen y esta imagen y la voy a
poner una encima de otra la voy a pilar aquí esto lo podemos hacer con la
función concatenate concatenate a la cual le podemos decir oye concaténame
esta imagen de aquí y esta imagen de aquí con lo cual esto nos estaría
volviendo un tensor que tendría de tamaño pues bat size por 256 por 256
por 6 que son dos veces el número de canales que tenemos a partir de ahí el
resto del discriminador pues no tiene mucha más ciencia vale es simplemente ir
siguiendo las pautas que te indica el paper que no es en este caso éste no es
el discriminador correcto sino éste de aquí el de 70 por 70 y que viene
especificado por un bloque de 64 128 156 y 512 después de la última capa se
aplica una convolución que tiene que ser unidimensional seguida de la función
sigmoide y igual que antes pues la capa de batch normalization no se aplicaría
al primer bloque de 64 todas las relaciones de tipo leaky y con pendiente
0.2 por tanto si nos venimos aquí vemos que lo que hemos definido es exactamente
eso tenemos el inicializador tenemos la primera capa
con el filtro de 64 filtro 128 256 y 512 al final tenemos que definir una última
capa que tiene que tener un único filtro de salida porque porque necesitamos que
sólo haya un canal en este caso la imagen que estamos generando pensemos que
es una imagen donde se va a definir por cada píxel de esa imagen si la región
de la imagen original pues parece real o no parece real en este caso es el único
sentido que tiene esta imagen y por eso solamente necesitamos un único canal de
información que lo conseguimos pues poniendo el filtro igual a uno el resto
se mantiene igual kernel size a 4 straight a 1 el kernel initializer que
hemos definido y el padding a 6 por tanto nuestro modelo el modelo
discriminador el que vamos a devolver pues es este de aquí vale tf queras
model inputs la en este caso le tenemos que definir que el input es el ini y el
gen que le estamos pasando arriba y el output es el resultado de la capa last
si queremos comprobar si el discriminador está correcto pues al
igual que hemos hecho antes lo que tenemos que hacer es instanciarlo
ejecutarlo y ver que todo está bien por ejemplo aquí faltan dos puntos
vale vale parece en principio que está bien y al igual que hicimos antes pues
también podemos comprobar qué tal funciona cogiendo al discriminador y
pasándole como input en este caso la imagen de entrada es decir la imagen que
le estamos pasando al generador y el resultado que hemos obtenido de antes
cuando hemos ejecutado el generador el output de lo que ha generado vale vamos
a pasarle estas dos imágenes y vamos a visualizar cuál es el resultado que
obtenemos si esto lo hacemos y todo va bien pues podemos ver que esto sería un
ejemplo del resultado que nos puede ofrecer el generador donde tenemos
diferentes píxeles con diferentes colores y por ejemplo pues aquí estaría
evaluando esta intensidad de azul fuerte nos estaría diciendo que el
discriminador que de momento no está ni entrenado pues está detectando toda
esta área de aquí todas estas estos parches de la imagen original como
poco realistas o falsos ok se entiende más o menos la idea perfecto con esto
ya tenemos los dos elementos importantes de nuestra red generativa
tenemos los dos bloques que conformarían el sistema de redes generativas adversarias
lo que faltaría ahora sería diseñar una función de coste que es lo que se
conoce como función de coste adversaria que básicamente la que va a unir el
resultado de una con el resultado de otra y va a ser que durante el
entrenamiento estén compitiendo ok para hacer esto lo que tenemos que hacer es
definir lo que van a hacer las funciones de coste y en este caso vamos a empezar
definiendo un objeto que nos va a servir para evaluar el resultado de las
imágenes que vayamos obteniendo aquí en este caso pues podemos aprovecharnos de
la función definida anqueras binary cross entropy que básicamente lo que
va a hacer es calcular la entropía cruzada de cada uno de los píxeles de
las imágenes que estamos obteniendo esto lo vamos a hacer y también vamos a
activar el parámetro frontlogits y le vamos a decir que se ponga true para que
las imágenes que nosotros obtengamos como entrada pues se normalicen
pasándolas por una función sigmoide para acotarlas al dominio de 0 y 1 algo
que además el paper nos pedía que teníamos que hacer si recuerdan aquí
antes leímos esta frase y nos decía he seguido de una función sigmoide en este
caso al activar el frontlogits igual a true pues consigue ese efecto a partir
de ahí pues vamos a evaluar el comportamiento del discriminador con dos
componentes diferentes por un lado tenemos el real loss y por el otro lado
tenemos el generated loss el real loss lo que está haciendo si se fijan es
utilizar el objeto de la función del binary cross entropy para computar para
evaluar la diferencia entre dos cosas por un lado tenemos el resultado del
discriminador al observar una imagen real y esto lo estamos comparando con
lo que sería el resultado idóneo ¿cuál sería el resultado idóneo? pues sería
si el discriminador estuviera observando una imagen real lo que debería estar
evaluando lo que debería estar generando como salida tendría que ser una
imagen como la que tenemos aquí arriba en la que todos los píxeles estuvieran
activados para decir esto es real esto lo estamos consiguiendo aquí diciéndole
oye calcula me la diferencia entre lo que has observado al observar la imagen
real y una matriz del tamaño de la imagen real que esté todo a unos ¿vale?
es una matriz donde todos los valores están a uno todos los valores a uno
indican que la imagen observada es real si el discriminador estuviera funcionando
bien esta imagen de aquí debería de parecerse a esta de aquí con lo cual el
discriminador estaría detectando que todo es real y la función de coste
debería de dar un error bajito al diferenciar esta con esta ¿se entiende
más o menos la idea? esta misma idea es lo que se hace cuando calculamos el
generated loss en el generated loss lo que estamos haciendo es comprobar el
resultado del discriminador al observar la imagen que ha sido generada por el
generador y en este caso lo que deberíamos de observar en lo que sería
el resultado idóneo para el discriminador sería una matriz del
tamaño de la imagen obtenida pero en el que todos los valores estuvieran a cero
o lo que es lo mismo que el discriminador estuviera diciendo a ver
todo lo que hay en esta imagen todos los píxeles que estoy observando son falsos
¿vale? todos los parches que estoy viendo aquí están mal son todos cero son
falsos si esto fuera así el comportamiento del generador si la
imagen que ha generado al observar el resultado del generador fuera parecida
a esta de aquí entonces también estaríamos obteniendo un error muy bajo
y el generated loss sería bajo. Estas dos componentes juntas estarían
evaluando si el comportamiento del discriminador es el que se espera o no
finalmente cogemos estas dos componentes las sumamos computamos el total
discriminator loss y este es el valor que definirá a nuestro discriminator loss
¿se entiende? parece muy complicado pero en realidad son conceptos muy muy
sencillos por el contrario la evaluación del generador va a ser diferente el
generador tiene dos objetivos uno de ellos es generar una imagen que sea
realista que se parezca al resultado a la imagen real que queremos conseguir si
está generando una margarita pues se tiene que parecer a la imagen final de la
margarita esto se entiende pero también su otra labor puesto que esto va a ser
una competición entre el discriminador y el generador va a ser conseguir que el
error del discriminador se maximice es por eso que en este caso el generator
loss pues le pasamos tres objetos diferentes por un lado le pasamos el
generated output la imagen que ha generado el generador y también la
imagen que esperábamos que generara la imagen real de la margarita y la
imagen de la margarita que él ha creado y por otra parte lo que le estaríamos
pasando es el discriminator generated output es decir de nuevo el mapa que ha
generado el discriminador al observar su imagen a partir de ahí pues vamos a
computar dos componentes de error diferentes por un lado es el error
adversario como tal que es la diferencia entre el resultado del discriminador al
observar la imagen generada y lo que él querría que fuera el resultado que
básicamente que el discriminador viera esa imagen y dijera oye todo esto son
unos todo esta imagen es verdadera ese tendría que ser el resultado idóneo
para el generador que básicamente se basaría en evaluar si está engañando no
al discriminador si se fijan esta componente de aquí es concretamente la
contraria al generated loss que hemos especificado arriba aquí poníamos
ceros y en este caso serían unos esta es la parte donde divergen las funciones de
coste y donde se establece toda la componente adversaria de nuestro
sistema por el contrario también tenemos el l1 loss que básicamente la
diferencia por píxeles en valor absoluto entre la imagen real que
queríamos generar y la imagen que hemos generado esto es básicamente computar
aquí la fórmula del error absoluto medio vale la diferencia entre las dos
imágenes valor absoluto y calcular la media con esto ya tendríamos las dos
componentes necesarias para calcular nuestro generator loss aquí no pasa
como en el discriminador donde las dos componentes se suman por igual sino que
tenemos un hiper parámetro lambda que nos va a servir para regular si queremos
añadir un mayor grado de de error de una componente u de la otra en este caso el
paper te especifica que un valor óptimo es un valor de lambda de 100 donde le
estaríamos dando 100 veces más peso al error l1 loss que al error adversario
vale pues tal cual lo explica el paper pues tal cual lo definimos nosotros aquí
con esto calculamos el total generator loss y este es el valor que va a devolver
nuestro generator loss con esto ya tendríamos casi todo lo importante de
cara a este tutorial implementado vale todavía falta una última celda que
quiero explicar pero antes voy a daros unas cuantas otras celdas con línea de
código que van a ser importante de cara a entrenar a todo el sistema pero de las
cuales no me quiero parar mucho porque si no este tutorial se va a ser eterno
pero bueno una de estas celdas es la siguiente vale además de definir los
optimizadores que esto sí es importante hacerlo vale en este caso estamos
utilizando adam como sistema para optimizar a todas nuestras funciones con
los hiper parámetros que nos especifica el paper
pues además de esto tenemos la siguiente línea de código vale que lo que estamos
haciendo aquí es guardar checkpoints guardar estados de la ejecución del
entrenamiento de nuestro sistema lo que nos va a permitir los checkpoints que lo
vamos a guardar en el check path que hemos definido al principio del tutorial
pues va a ser que guardemos el estado de los optimizadores tanto del generador
como el discriminador y también de los objetos generator y discriminator con
esto si por ejemplo tú estás trabajando con google colap y el sistema se te cae
y a lo mejor ibas por la epoch número 113 pues te va a permitir poder
restaurar utilizando la siguiente línea de código
te va a permitir conectarte a tu google drive donde has ido guardando los
diferentes estados de tu sistema y obtener el último checkpoint que haya
guardado a lo mejor fue en la época número 100 con eso pues no tienes que
volver a repetir todo la ejecución del sistema y es algo bastante cómodo a
tener en cuenta otra celda interesante a copiar es la siguiente de aquí
generate images que básicamente lo que nos va a servir es para ir evaluando
cómo es el comportamiento del modelo generador que estamos entrenando durante
el entrenamiento básicamente este esta función lo que nos permite es pasarle al
generador imágenes de nuestro set de prueba como podemos ver aquí vale
estamos cogiendo test input y se lo estamos pasando a nuestro modelo y al
mismo tiempo también podemos guardar estos estas imágenes que estamos
generando en algún directorio que nosotros especificamos y podemos también
visualizar el input el output y el resultado esperado vale así que copien
estas líneas de código porque nos van a servir a continuación y finalmente el
último bloque de código que le voy a pedir que copien es el siguiente de
aquí que es el que define nuestra rutina de entrenamiento vale a esta función lo
que le pasamos es el dataset con el que queramos trabajar ya sea el
entrenamiento o el de test y el número de épocas que queramos que se cumplan a
partir de aquí pues se va a iterar vamos a ir recorriendo todas las épocas y
vamos a coger para cada imagen y cada cada imagen de entrada y cada imagen
objetivo en nuestro dataset y se la vamos a pasar una función train step
que vamos a pasar a definir ahora vale que es la última que nos faltaría por
implementar además se va a ir visualizando pues información de en qué
época estamos en qué este estamos y también cada vez que acabemos una
época pues vamos a tomar imágenes de nuestro dataset de prueba y vamos a
llamar a la función anterior a generate images le vamos a pasar el generador el
input del target y le vamos a pedir que nos guarde las imágenes del dataset de
prueba en una carpeta de nuestro directorio de google drive con esto pues
podemos ir guardando del dataset de prueba la evolución de cómo se van
generando las imágenes para obtener luego animaciones chulas de cómo ha sido
el entrenamiento por tanto lo último que nos faltaría para acabar todo este
embrollo de código sería definir este train step vale esta va a ser el último
bloque de código que vamos a implementar y donde básicamente vamos a terminar de
conectar todos los módulos que hemos ido implementando del generador
discriminador que esto ya están conectados en la función de coste pero
que todavía no hemos dicho cómo se van a ir ejecutando en cada paso de el
entrenamiento entonces vamos a crearnos una celda aquí arriba y vamos a crear la
función train este esta función cada vez que la llamemos como podemos ver le
estamos pasando en la imagen de entrada el garabato de nuestra flor y la imagen
resultante que nos gustaría obtener porque recordemos que esto no deja de
ser aprendizaje supervisado entonces en este caso pues lo vamos a definir
exactamente esto no input image y target y entonces cómo se sucede la obra de
teatro pues la obra de teatro comienza con el generador el generador va a tomar
como entrada efectivamente la imagen de entrada de nuestro sistema el garabato
de la flor esta imagen pues va a ser comprimida por el encoder va a ser de
comprimida por el decoder y vamos a obtener una imagen de salida esto es el
output image en todo este proceso como estamos entrenando al sistema pues le
vamos a decir que efectivamente los parámetros del generador se entrenen
en este paso a partir de ahí el discriminador pues va a tener que hacer
dos tareas primero va a ser observar lo que ha generado el generador y obtener
el output gen del discriminador esto lo hacemos pues llamando al discriminador
y pasándole por una parte el output image que ha generado antes en el paso
anterior el generador y por otro lado el input image porque recordemos que es un
discriminador condicional y que también tiene acceso a la imagen de entrada de
nuevo como queremos que se entrene vamos a especificar training true el otro paso
que tiene que hacer el discriminador va a ser tomar la imagen real la imagen
objetivo y esto lo vamos a llamar output
target target discriminator y lo que va a obtener aquí es va a observar pues si
el target la imagen target la imagen que nosotros esperamos va a evaluarla a ver
si es real o no es real ok y en este caso también accede al input image con estas
tres componentes ya calculadas pues ya lo que podemos hacer es llamar a nuestras
funciones de discriminator loss y generator loss que hemos definido
anteriormente y que por cierto no hemos ejecutado vamos a ejecutarla
ejecutarla ejecutarla
ejecutar esta también esto de error
vale esto lo arreglamos ahora generate images vale perfecto y
estamos aquí entonces por ejemplo el discriminator loss discriminator loss que
recibe el discriminator real output y el discriminator generated output primero
el real y luego el generado pues vamos a definir al discriminator loss como el
resultado de llamar a la función de coste discriminator loss que tenemos
aquí abajo y de pasarle el resultado de observar la imagen real la imagen target
en este caso lo tenemos el resultado lo tenemos en output target me lo copio de
aquí por un lado está y por otro lado está de aquí de esta manera ya tendríamos
conectado el resultado del discriminador con su función de coste y ya tendríamos
calculado lo que sería el discriminator loss por otro lado el generator loss si
recuerdan lo que hacía uso lo que tomaba como entrada era por una parte la
observación del discriminador a la imagen que él había generado la imagen
que había generado y la imagen objetivo que estamos buscando pues esto mismo lo
que le vamos a pasar vamos a pasarle al
generator loss estoy perdiendo la voz de tanto hablar
tiqui tiqui tiqui tiqui ah vale también ejecutado hasta hacerla con lo cual no
me deja acceder de nuevo tomamos el output generator discriminator
tomamos el output image y tomamos el target image que es la imagen objetivo a
partir de aquí si se dan cuenta ya tendríamos ejecutados todos los pasos
para computar esta función de coste pero recordemos que en una red neuronal en una
arquitectura simple pues tú tienes tu input consigues tu output consigues la
evaluación de la función de error pero todavía nos faltaría hacer el proceso
de optimización no dar ese paso de back propagation etcétera esto cómo lo vamos
a hacer aquí pues nosotros lo que tenemos que hacer es decirle oye tenemos
que coger los gradientes tienes que calcular los tus solos tensor flow y
que te lo van a hacer automágicamente que esos maravillosos y a partir de esos
gradientes pues te tienes que optimizar cómo especificamos eso pues primero
tenemos que definir un par de objetos aquí arriba estos objetos son el
gradient tape y ahora les explico que es del generador en tape y por otro lado el
gradient tape de nuevo del discriminador vale que son estos objetos que es el
gradient tape bueno si recuerdan nosotros estamos utilizando tensor flow
o podríamos utilizar pytorch porque nos aporta una ventaja muy grande frente a
implementarlo todo desde cero más allá de la happy y de todas las funciones que
nos ofrezcan y es que estos sistemas son sistemas de autodiferenciación esto
significa que todas las operaciones que nosotros estamos registrando no tenemos
que calcular toda la derivada de las operaciones que estamos registrando en
nuestra arquitectura si recuerdan cuando hicimos el tutorial de cómo programar
una red neuronal de t0 había un apartado en el que cuando implementamos back
propagation pues teníamos que calcular a papel y boli pues todas las derivadas
parciales y luego implementarlas este paso es un coñazo y por suerte todos
estos sistemas como digo tensor flow pytorch nos ofrecen esta solución de
autodiferenciación la autodiferenciación se puede lograr de
maneras muy diferentes con el grafo computacional con los gradient tapes y
actualmente tensor flow pues ha implementado este sistema de gradient tape
que creo que es similar a lo que utiliza pytorch vale entonces cuando nosotros
aquí estamos definiendo el objeto gradient tape es básicamente el objeto
que nos va a permitir acceder a los gradientes y optimizar nuestros pesos
es básicamente el paso de back propagation pero de una manera mucho más
guay por ejemplo si quisiéramos calcular cuáles son los gradientes del generador
pues lo que podríamos hacer es generar una variable aquí generator grads y
utilizando el objeto arriba el generator tape le podemos decir registrame las
operaciones y cálculame los gradientes de pues esos gradientes de optimizar mi
función de coste del generador para optimizar todos los parámetros que
conforman a mi generador vale entonces le tenemos que especificar cuál es el
generator loss que en este caso tenemos al objeto generator loss y también le
tenemos que especificar que el generador tiene todas estas
trainable variables que serían todos los parámetros de nuestro generador
con esto pues ya tendremos calculada de manera automágica pues todos los
gradientes que necesitamos para optimizar en cada paso del entrenamiento a nuestro
generador de la misma manera pues podemos registrar lo mismo para el
discriminador calculando discriminator grads que sería el discriminator tape
punto gradients del discriminator loss y esto sería el discriminator trainable
variables ok con esto ya tendremos calculado los gradientes del generador y
del discriminador ahora solamente faltaría hacer el paso de optimizar no
aplicar esos gradientes y actualizar los valores de los parámetros en función
de estos gradientes que hemos calculado esto lo podemos conseguir pues llamando
al optimizador que hemos definido anteriormente que si se acuerdan lo
tenemos aquí arriba el adam optimizer y llamando a una función que es apply
gradients que en la que se van a encargar de aplicar estos gradientes en
función de cómo el optimizador decida trabajarlos según como sea la manera de
trabajar de adam utilizando momentum utilizando pues las diferentes técnicas
que tiene para eso lo que te pide que le pases es por un lado los gradientes que
has calculado que hemos calculado anteriormente el generador grads y por
otro lado le tenemos que pasar cuáles son esas de nuevo variables que vamos a
optimizar vale las mismas que hemos especificado aquí arriba se las
especificamos aquí abajo las unimos latos con él con el cip aplicamos
gradientes y con esto estaríamos optimizando el generador de la misma
manera vamos a hacer lo mismo discriminator optimizer punto apply
gradients cip discriminator grads y discriminator trainable
ejecutamos esto y a ver cuántos errores hay pues ninguno me extraña que no me
haya equivocado escribiendo esto vale con esto ya tendremos especificado lo que
sería un paso en nuestro entrenamiento al mismo tiempo pues podríamos
aprovechar para añadir print si queremos visualizar cómo va evolucionando el las
funciones de coste en el tiempo etcétera en nuestro caso yo creo que ya es
suficiente y que lo que nos interesaría ver es si el sistema se va a entrenar o
no se va a entrenar vale si va a haber algún fallo o no entonces para eso lo
único que tenemos que hacer pues va a ser llamar a la función train que
tenemos aquí arriba sobre el data set de entrenamiento vale y vamos a dejar que
el sistema se vaya entrenando poco a poco vamos a especificarle unas 100
épocas aquí tienen una serie de parámetros que
pueden ir ajustando por ejemplo esto de aquí si quieren indicar pues cuántas
cada cuántas épocas quiere guardarte un checkpoint de seguridad yo en mi caso lo
voy a dejar a 50 porque necesito ahora mismo le damos aquí y vamos a ver si la
cosa funciona
vaya esto no está definido gradient tape en la línea aquí arriba vale me
faltará un import no me falta un tensor flow y un tensor ejecutamos de nuevo
ejecutamos de nuevo ejecutamos de nuevo
gradient tape object no tiene atributo gradients gradient tape
vamos a quitarle la s
vale ahora parece que sí está funcionando la cosa de nuevo
donde ha saltado ahora en el discriminator discriminator tape gradients
ah vale le he quitado la s donde no debía aquí arriba vale
vamos a ejecutar de nuevo
vale parecería que ahora sí está funcionando el sistema
he puesto 400 imágenes pues va a tardar un poco en ejecutarse de hecho voy a
parar un momento esto y vamos a hacer un cambio estético que va a ser meter el
clear output dentro del bucle del for para que no se vaya sumando esto sino
que se vaya desapareciendo el texto y sea más fácil de leer y no está haciendo
scroll
mejor recuerden que es importante tener el entorno de ejecución puesto en gpu
para que la cosa se acelere por hardware y sea mucho más rápido en
entrenamiento de hecho vamos a poner una última cosa que me acabo de acordar que
les dije que íbamos a tratar y es que si se acuerdan antes les comenté que
podíamos decorar funciones con este con este con esta etiqueta de aquí vale esto
lo que te permite es que el sistema en vez de ejecutarse en eager mode se
ejecute en autograd vale que haga el gráfico computacional y te optimice todo
tu código esto se lo podríamos ir cascando a cada
una de las funciones y si se pudiera optimizar pues se optimizaría lo cual
siempre está bien pero lo bueno de tensorflow es que si tú esto se lo
aplica a una función que va a llamar a otras funciones automáticamente pues por
esta dependencia que existe el tefe punto function se va a aplicar a todas
las funciones en cascada todo lo que vamos a hacer es venirnos para acá y le
vamos a aplicar este decorador no había copiado se vamos a aplicar aquí a
nuestro amigo train step del cual surgen el resto de funciones de nuestro sistema
con lo cual con suerte a lo mejor el entrenamiento ahora va un poquito más
rápido antes de darle debemos de actualizar una cosita aquí y es que el
directorio donde vamos a ir guardando las imágenes le faltaba una barra para
tener la dirección bien especificada y al mismo tiempo yo me he dado cuenta que
como ya en output tengo muchos archivos de otras pruebas que he ido haciendo en
vez de sobre escribirlo y que se tenga que sincronizar con google drive prefiero
crear una carpeta aparte por eso creado un directorio ya en google drive que se
llama output b y ahí va a ser donde vaya colocando yo las nuevas imágenes que se
generen vamos a darle para que se guarde vamos a darle aquí también porque es
gratis y aquí también hace falta modificar esta línea hay que coger y
nos la llevamos aquí abajo y también tenemos que poner aquí abajo img y más
igual uno para ir incrementando el índice de las imágenes que estamos
utilizando para guardar los archivos vale porque si no estaríamos tomando el
índice de la del bug anterior y fallaría pues todo estrepitosamente vamos a darle
y ahora sí que sí vamos a comprobar si utilizando tf.function en la ejecución
va mucho más si va mucho más ágil vale y podemos ver que efectivamente la cosa
va mejor que antes notablemente mejor y en parte tiene sentido porque al final el
tf.function lo que va a hacer es decir oye déjame tu grafo computacional dame
todas tus operaciones que te las voy a compilar y te voy a optimizar para que
pueda ir lo más rápido posible entonces lo hemos hecho lo hemos colocado casi en
la raíz de toda la ejecución que se está haciendo porque de esta función
salen todo el resto de funciones de nuestro sistema y como podemos ver pues
esto se va ejecutando correctamente poco a poco el generador va aprendiendo a
generar mejores imágenes como podemos ver aquí además hay que tener en cuenta
que estas que estamos visualizando son las que vienen del dataset de prueba
vale lo podemos ver aquí esto significa que nuestro modelo está aprendiendo a
generar el resultado que queremos con imágenes que no ha visto anteriormente y
esto está pues bastante bien vale si no venimos a google drive pues también
podemos comprobar cómo en output b se van guardando poco a poco las imágenes
que se van generando esto depende un poco de cuánto tarde en sincronizarse
google drive y nada más en principio cuando todo vaya avanzando también
deberían de ir apareciendo los checkpoints que están guardando así que
en caso de que haya algún fallo pues puedes recuperar tu tu entrenamiento y en
principio pues ahora debería de dejar tu sistema ejecutar por un periodo largo
de tiempo esto puede variar dependiendo del tipo de
problema que quieras afrontar cuánto tanto estés utilizando etcétera yo por
ejemplo con este dataset he hecho pruebas y por ejemplo con unos 400
imágenes de entrenamiento lo cual pues tardaría 10 veces más que la ejecución
que estamos haciendo ahora he tardado en cumplir unas 300 400 épocas unas 5 6
horas vale y con eso ya ha empezado a obtener algún resultado bastante
aceptable al menos para el tipo de problema que estamos teniendo que
resolver entonces dependiendo del tipo de problema que tú quieras afrontar si
es para la competición o para cualquier otra cosa aquí por ejemplo en el paper
te ponen diferentes tipos de problemas que ellos han probado y cuántas imágenes
de entrenamiento han utilizado cuántas épocas lo han entrenado con qué bat size
cuánto ha tardado etcétera con lo cual esto está muy bien echarle un vistazo si
esto lo dejan entrenar durante un ratito este no este de aquí si esto lo dejan
entrenar durante un ratito pues podrán obtener finalmente resultados como este
como ven tras haber entrenado nuestro sistema durante varias horas pues
podemos empezar a obtener resultados medianamente realistas que podrían
parecer flores sobre todo si lo veis a 10 metros de distancia vale aquí esto no
sería un punto final sino que sería realmente el comienzo para empezar a
experimentar yo aquí me plantearía si a lo mejor mi data set pues tiene que
depurarse un poco y quitar algunas flores que no son representativas del
data set en el que el fondo a lo mejor está mezclado o no pues no aparece en
hojas sino que aparece cielo aparece en ladrillos a lo mejor plantas que no son
del estilo que quiero yo a lo mejor estoy buscando plantas redondas y hay
plantas alargadas a lo mejor mi arquitectura le podría introducir algún
cambio que la mejorara que fuera más óptima a lo mejor mi código tiene algún
fallo también habría que verlo a lo mejor ha salido una arquitectura mejor y
podría probarla o ajustar mejor los hiperparámetros como ven hay
muchísimas cosas que se pueden hacer y este es solamente el comienzo de empezar
a trabajar con una arquitectura de dippler como ven el tutorial es complejo
pero no es complicado es decir hay muchas partes funcionando y es muy
importante saber que estas partes tienen que conectarse e integrarse
correctamente pero el código en sí mismo entenderlo no es tan complicado por
eso les invito a que hagan pruebas que lo toqueteen que cambien cosas que
prueben otro tipo de datos y sobre todo que participen en la competición que
tenemos abierta y que va a cerrar el 14 de septiembre donde pueden ganar esta
tarjeta gráfica lo importante no es tanto la tarjeta gráfica sino que se
enfrenten a un problema real planteado por ustedes con esto espero que hayan
disfrutado del tutorial que hayan aprendido mucho y ya saben si este
contenido lo valoran de verdad si ven que esto les está aportando
conocimiento que realmente tiene valor para vuestra vida profesional y vuestra
autoestima y autorrealización y estas cositas pues sepan que pueden apoyar al
canal de youtube a través de patreon ya somos 120 patreons es una auténtica
pasada lo agradezco muchísimo y además a partir de septiembre vamos a empezar
con un chat de telegram privado para todos los que sean patreon para que
puedan tener un canal de comunicación más cercano conmigo y podamos compartir
cosillas por ahí a partir de ahí pues nada sepan que dot ccv sigue de
vacaciones y que volveremos el 15 de septiembre cuando cerremos ya la
competición vale les animo a participar que disfruten del verano y nos vemos en
la tercera temporada de dot ccv