logo

Dot CSV

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

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

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

Hola chicos y chicas, bienvenidos a DOTCSV, tu canal sobre Inteligencia Artificial en el que hacemos
cosas muy guays como esta que estamos haciendo hoy. Estamos de nuevo en un IA Notebook. ¿Qué
es un IA Notebook? ¿No sabes lo que es? Eso es que te has perdido el primero que subimos al canal
hace ya varias semanas, pero bueno, te lo explico. Esta es la sesión del canal donde tú y yo nos
sentamos aquí en frente del ordenador con el teclado delante a picar un poco de código y ver
cómo podemos implementar estos sistemas de Inteligencia Artificial, de Machine Learning que
poco a poco estamos viendo en el canal, ya sea en los Data Coffee o en los vídeos de Aprendiendo
Inteligencia Artificial. Pues aquí es donde vamos a ver la parte práctica, la parte de sentarnos y
ponernos a trabajar un poco. Hoy traigo un tema muy interesante que para aquellos que hayan visto el
último Data Coffee que subí la semana pasada, pues ya lo conoceréis. Y como digo, es un tema
muy interesante, son los ataques adversarios que son estas formas, esta técnica que existe para
poder engañar a una red neuronal. Tenemos que el campo del Machine Learning actualmente
se sustenta principalmente en un tipo de algoritmos, en una familia de algoritmos que
son las redes neuronales y son unos algoritmos que funcionan muy bien, pero que también son
vulnerables a un tipo de ataques que son los ataques adversarios. Estos ataques son muy
interesantes porque te permiten engañar o hackear a una red neuronal para que, para ciertas imágenes
que normalmente suelen funcionar bien, por ejemplo una foto de un gatito, pues puedes hacer que la
red neuronal te diga esto es un gato, pero también puedes engañarla para que te de un resultado
completamente diferente. Es como si haces un sistema de detección de caras, pues que puedas
suplantar la identidad de otra persona, por ejemplo. En el vídeo del Data Coffee que subí la semana
pasada, lo explico todo con detalle y explico más o menos cómo va a ser la técnica que vamos a
desarrollar hoy. Y hoy lo que vamos a hacer es eso, vamos a intentar implementarlo y vamos a ver cómo
esta técnica, cuando la implementas, pues puedes llegar incluso a entender mejor cómo es el
funcionamiento interno de estas redes neuronales, y yo creo que va a ser algo interesante que ver.
Una cosa que sí quiero mencionar es que a diferencia del IA Notebook anterior, donde hicimos
el sistema este de regresión lineal, donde implementamos el método de cuadrados ordinarios,
en el vídeo de hoy va a ser un poco más complejo, de hecho va a ser un nivel medio avanzado o nivel
medio, pero bueno, vamos a estar utilizando una serie de librerías que son un poquito más
complicadas que es TensorFlow, que es una librería que se utiliza para hacer, para diseñar arquitecturas
de Machine Learning y que puedas implementarlas y te da pues todas las herramientas para que todo
el proceso de optimización se realiza automáticamente. Y también vamos a utilizar Keras,
que es una API que funciona encima de, en este caso de TensorFlow, y que te simplifica el trabajo
de utilizar esa librería. Es como una manera más sencilla de trabajar con TensorFlow. ¿Qué pasa?
Que si no conoces el funcionamiento de estas dos librerías, pues tampoco va a ser hoy un tutorial de
introducción a TensorFlow, que eso ya vendrá en un futuro, sino que va a ser un poco implementar
el ataque adversario y ya está. Entonces si no tienes conocimiento de estas dos herramientas,
te recomiendo que te quedes igualmente y que poco a poco vayas viendo cómo vamos haciendo el código,
porque siempre algo se queda y también porque vamos a repasar un poco los conceptos teóricos
que vimos en el vídeo del Data Coffee. Entonces si no tienes el nivel, pues te recomiendo que te
quedes igualmente y que puedas extraer lo que puedas de este vídeo. Nada más, no me quiero
enrollar porque siempre me enrollo más que a una persiana y lo divertido aquí es sentarnos a
programar. ¿Qué vamos a hacer? Bueno, vamos a intentar trucar el sistema, la red neuronal Inception
V3, que también la mencioné en el Data Coffee. Esta red es un clasificador de imágenes entrenado
sobre el dataset ImageNet y esta es una red diseñada por Google, que tiene un muy buen rendimiento
en la tarea de clasificar imágenes y en este caso estamos hablando de que estamos clasificando
imágenes en mil categorías diferentes, ¿vale? Es una cantidad de clases brutal y la verdad que
ImageNet consigue un gran, gran rendimiento haciendo esta tarea de clasificación. ¿Qué vamos a hacer
nosotros? Pues primero vamos a probar que este modelo funciona, nos vamos a descargar el modelo
ya pre-entrenado de internet, es decir, el modelo ya con los parámetros ajustados perfectamente para
que podamos utilizarlo, para que tú le metas la imagen y te diga cuál es el resultado y después
vamos a intentar generar una imagen adversaria, un ataque adversario que pueda engañar a esta
red, ¿vale? Y así ya podemos ver un poco las dos vertientes, cómo descargar un modelo, utilizarlo
y cómo romperlo, que siempre está bien aprender a romper las cosas. ¿Qué hacemos entonces? Pues lo
primero que tenemos que hacer es cargar el modelo. Como he dicho, hoy vamos a trabajar con dos
herramientas que no hemos visto antes aquí en el canal, una es tensorflow, que la vamos a cargar aquí,
import tensorflow y la otra es Keras, Keras directamente nos vale. Además vamos a seguir
utilizando las librerías que ya vimos la semana pasada, que era Matplotlib, que la usamos para
hacer gráficas, la cargamos como plt y también nos va a hacer falta Numpy, que es una librería básica
para el uso de matrices y tal, como ya vimos también en el anterior ia notebook. Le damos a cargar,
ejecutamos y como vemos ya nos carga todas las librerías y nos sale un mensaje aquí que dice
using tensorflow backend. Este mensaje nos lo está mostrando Keras, porque como he dicho Keras es una
API que funciona encima de tensorflow pero que también podría funcionar encima de otros frameworks,
eso lo que te permite es que tú puedas hacer tu código con Keras y tener portabilidad entre
diferentes frameworks y eso está muy muy bien. En este caso este mensaje nos indica que efectivamente
estamos trabajando con tensorflow. Una vez tenemos las librerías cargadas lo siguiente que vamos a
hacer es cargar nuestro modelo. Afortunadamente Keras en este caso nos proporciona el modelo
pre-entrenado, es decir, no tenemos que ir a internet y buscar en github el inception v3 ya
pre-entrenado, sino que directamente lo podemos sacar de la librería de Keras. Entonces para eso
tenemos que importar el modelo desde creo que es applications. Si nos fijamos aquí dentro de
keras.application y le damos a tabulador podemos ver el contenido de este módulo y vemos que tenemos
diferentes librerías, todas estas de aquí son librerías conocidas, tenemos resnet, inception,
mobile net, de nuevo resnet otra versión, vgg y en este caso la que queremos utilizar es inception
v3, perfecto. De este módulo lo que vamos a cargar, debería ser así realmente, de este módulo
cargamos por un lado el modelo inception v3 y por otro lado vamos a cargar una funcionalidad que
luego os explico exactamente para qué sirven. Aprovechando que estamos haciendo los import,
también vamos a importar otra cosa que nos va a hacer falta luego de Keras, que es el backend.
Si no me equivoco lo podemos importar, no, a ver, franqueras.backend, franqueras import backend
as k, ¿vale? Esto ya luego veremos para qué lo vamos a utilizar. Le damos a control enter para
cargar esta celda y ya tendríamos cargado las librerías que vamos a utilizar en este tutorial.
Vamos a crear una nueva celda, yo siempre estoy dando el botón aquí pero realmente una forma más
cómoda es utilizar el teclado y pulsar la tecla B, así podemos trabajar mucho más rápido y si
quisiéramos borrarla pues le daríamos a escape y a la X, B para crear, X para borrar. Después de
jugar aquí con la celda, lo que vamos a hacer es cargar el modelo, ¿vale? Para eso la funcionalidad
que hemos cargado aquí, inceptionv3, es la que nos va a permitir cargar el modelo dentro de una
variable. A esa variable la vamos a llamar iv3 de inceptionv3 y pues vamos a poner esto,
inceptionv3 y le damos a control enter. Va a tardar un poquito en cargar la librería porque piensen
que es un modelo muy muy grande, ahora lo vamos a ver y si es la primera vez que lo estás ejecutando
en tu ordenador posiblemente tarda un poco más porque lo tenga que descargar incluso de internet.
Seguramente aparezca una barra de descarga con un porcentaje y estará de entorno a un minuto,
un minuto y medio. En mi caso esto no debería demorarse más de 30 segundos por ser la primera
vez que lo cargo en este cuaderno. Ahí está, perfecto. Una vez lo tenemos cargado, podemos ver
que ya tenemos dentro de esta variable diferentes funciones que podemos utilizar, por ejemplo evaluar,
fit, getlier, getweights, vale, tenemos muchas cositas aquí, pero lo que les quiero enseñar es
esta variable de aquí, summary. Si nosotros hacemos un print del contenido de esta función,
lo que vamos a obtener es como un resumen de... ay mierda, le he vuelto a dar a cargar el modelo.
Esto debería ponerlo en una zelda aparte para que no esté recalculando cada vez que lo hago.
A ver, mejor así. Zelda, vamos a quitar el output.
Vale.
Y aquí sí, si hacemos un print de iv3.sumary, lo que obtenemos es un resumen de cómo es la
composición, cómo es la topología de la red neuronal Inception v3. Por ejemplo, la primera
línea está de aquí, hace referencia al primer componente, que sería la primera capa. Esta sería
la segunda capa, la tercera capa, etcétera, etcétera, etcétera. Lo quiero enseñar porque
es que miren la cantidad de capas que tiene este modelo. O sea, estamos hablando de, pues no sé si
100 capas, más de 100 capas, la verdad que no lo sé. En total pone que tenemos 23 millones de
parámetros, que nuestro modelo tiene 23 millones, casi 24 millones de parámetros que podemos
entrenar. Pensad que cuando nosotros tenemos un modelo para poder entrenarlo, para poder
entrenar sus parámetros, entre más parámetros tiene más cantidad de datos hace falta para que
a partir de esos datos podamos hacer el proceso de optimización y de reajustar todos estos parámetros.
Imagínense la cantidad de datos y de tiempo que hace falta para entrenar este modelo. Una cantidad
brutal. Afortunadamente, pues tenemos la oportunidad de descargarlo de esta manera, ya pre-entrenado,
y que podamos utilizarlo. Por ejemplo, si tú quieres hacer una aplicación móvil, tú puedes
descargarte este modelo y utilizarlo directamente. Y eso no te va a llevar mucho tiempo. Es decir,
ya verán ahora que hacer una predicción con el modelo ya entrenado es muy, muy sencillo. La verdad
que es una muy buena manera de crear aplicaciones que se aprovechen de Machine Learning sin tener
que contar con tantos recursos, utilizando ya modelos pre-entrenados. Muy bien, ¿qué hacemos ahora?
Bueno, lo que queremos hacer primero es probar si nuestro modelo funciona. Entonces, lo primero
que necesitamos sería una imagen. Para cargar las imágenes lo que vamos a hacer es importar otro
módulo de Keras que se me había olvidado, que se llama Keras Pre-Processing. También Python tiene
funcionalidades para cargar imágenes, pero bueno, en este caso ya que estamos tirando con Keras,
pues vamos a ver las diferentes cosas que nos ofrece. Entonces ponemos Keras Pre-Processing Import
Image. Lo ejecutamos y ya lo tenemos cargado. Entonces, dentro del módulo Image podemos cargar
en una variable X. Tenemos diferentes herramientas. En este caso a nosotros nos interesa una que es
Load Image, que básicamente lo que nos permite es a partir de la extensión poder cargar una imagen
que tengamos en nuestro ordenador. Yo para este tutorial ya tengo dos imágenes descargadas. Ustedes
tendrían que buscar las que les interesa por internet. Tengo aquí por la representación de
los dos objetos que más nos gusta a la humanidad. Por un lado tenemos gatitos recién buscados de
internet y por el otro lado tengo jarra de cerveza. Yo creo que con eso tenemos un resumen de lo que
la mentalidad humana ha desarrollado durante todos estos años. Entonces vamos a utilizar estas dos
imágenes para ver si nuestro módulo es capaz de predecirla. Entonces yo esto lo tengo dentro de
ia-notebooks, ia2 si no me equivoco y se llama virjpg. Lo ejecuto y ahora mismo tendría cargado
la variable x, la imagen. Como nosotros lo que queremos es trabajar no con un fichero de tipo
imagen, sino que queremos trabajar con datos numéricos, es decir queremos que nuestra imagen
esté representada por una matriz bidimensional o por un tensor, que un tensor es una matriz
multidimensional. Pues en este caso lo que vamos a usar es otra función del módulo Image, que es
ImageToArray. Con esto automáticamente, pues en vez de tener la imagen cargada como un fichero
de imagen, pues lo tenemos cargado como una matriz de valores. Vamos a ver qué forma tiene esta matriz.
Como vemos tiene tres dimensiones diferentes, en las cuales una dimensión hace referencia a
la altura de la foto, otra dimensión hace referencia al ancho de la foto y otra dimensión
te dice que tiene un valor de 3 y hace referencia a la profundidad de los píxeles. En este caso
estamos tratando con imágenes de colores, con lo cual los canales de color son RGB. Si estuviéramos
trabajando con imágenes en blanco y negro, pues este canal sería 1 o si estuviéramos trabajando
con RGBA sería 4, si tuviéramos más mapas como por ejemplo un mapa de profundidad, un mapa de
cualquier otro tipo, pues tendríamos más canales. En este caso vamos a adelantar trabajo porque yo
ya sé que por ejemplo para meter las imágenes dentro de la red neuronal Inception V3, pues hace
falta que esta matriz tenga una serie de requisitos determinados. Por ejemplo nos va a interesar que
la imagen tenga una proporción de 299x299 píxeles, con lo cual lo primero que vamos a hacer es un
reshape de la imagen. Para eso lo que podemos hacer es utilizar cuando cargamos la imagen,
podemos referenciar y decir target size, quiero que le pongas un tamaño de 299x299 píxeles,
con lo cual si ahora miramos de nuevo cuál es la forma de x, vemos que efectivamente la estamos
cargando redimensionada, ahora ya no sería el tamaño que tenía antes sino sería 299x299.
Otra cosa que les enseñé antes que esta matriz tiene, cada uno de estos números representa el
valor, la intensidad de cada uno de los píxeles en cada uno de sus canales, y como vemos estos
valores van de 0 a 255. Inception V3 utiliza un formato diferente, es decir utiliza un formato en
el que la intensidad va de menos 1 a 1, con lo cual lo que tenemos que hacer es rescalar el rango
de intensidades de la matriz x. Para eso, para cambiar de 0 a 250 a el rango menos 1 a 1,
y lo que tendríamos que hacer sería dividir entre 255, tendríamos que restarle 0 con 5,
con lo cual ya estaríamos en el rango, o sea si dividimos entre 255 estaríamos entre 0 y 1,
restamos 0 con 5 estaríamos menos 0 con 5, 0 con 5, ya lo tendríamos centrado y ahora lo que
tendríamos que hacer es multiplicar por 2. Ahí estaría. Como ven con esto, pues lo que estamos
haciendo es un cambio de rango, un cambio de base, no sé cómo se define matemáticamente,
de 0 a 255 estamos pasando a menos 1, 1. Y esto lo podemos ver si hacemos de nuevo un print de x.
Efectivamente ahora los valores van de menos 0 con 92 hasta 1, lo tenemos por aquí. Muy bien.
Vale, la última cosa que tenemos que tener en cuenta es que la red neuronal nos exige que le
pasemos como parámetro de entrada un tensor que tiene casi casi las mismas dimensiones que tiene
la matriz x, que en este caso son las que vimos antes, está aquí. Pero en este caso tiene una
dimensión más, es decir, sería el tensor que nos piden tendría cuatro dimensiones diferentes,
en la cual la primera dimensión hace referencia al tamaño del lote, al batch size. Esto quiere
decir que esta variable, esta dimensión, se refiere a si nosotros queremos meterle de un único pase,
queremos meterle más de una imagen, por ejemplo, le queremos poner 10 imágenes. Eso sería una
nueva dimensión. Tendríamos 10 imágenes con una altura, un ancho y una profundidad de píxel. En
este caso eso es una dimensión que nosotros tendríamos que incluir. Para eso lo que vamos
a hacer es un reshape de la variable x, donde añadimos esta nueva dimensión, lo ponemos como
1 y el resto de dimensiones pues serían exactamente iguales a las que teníamos en la variable x,
es decir, xshape 0, xshape 1 y xshape 2. Vamos a comprobar que está correcto. Efectivamente,
esta es la dimensión que nos exige el modelo Inception V3 para nuestro modelo, para los
valores de entrada. Si se preguntan de dónde saca esta información, pues esto lo pueden buscar
cuando descargan el modelo de internet. Pueden buscarlo en la página de Google donde está publicado
la información del modelo o también lo pueden encontrar en la documentación de Keras, ya que
te proporcionan el modelo, pues también te proporcionan la información de cómo tienes que
pasarle la variable de entrada. Muy bien, ya tendríamos la variable x como la necesitamos,
con lo cual ya se la podríamos pasar a nuestro modelo. Recordemos que nuestro modelo lo tenemos
en la variable IV3 y vamos a utilizar la función predict() para pasarle el valor de entrada
a x y con eso obtener el resultado de salida de nuestro modelo. Vamos a ejecutarlo y a
ver qué pasa. Y me ha crasheado todo. Se me ha reiniciado el kernel del Jupyter y he
tenido que volver a cargar todo. Llevo 10 minutos offline, pero bueno, ya estamos de vuelta.
Ya he podido cargar el modelo nuevo y le he dado a predecir. Entonces vamos a continuar.
Tenemos aquí ahora mismo en la variable y el resultado de haberle metido nuestro tensor
x que es la imagen de la cerveza. Vamos a ver cuál es el resultado que ha predicho.
Para ver si más o menos van siguiendo un poco el ritmo de lo que estamos haciendo, un buen
ejercicio es intentar siempre predecir cuáles son las dimensiones que tienes que esperar
de las diferentes variables que estás tratando. Porque una cosa que está clara es que tensor
flow, llamándose tensor flow, que es flujo de tensores, una cosa que no dice su nombre
es que vas a trabajar mucho con los tensores, que como digo son matrices multidimensionales.
Y siempre una cosa muy importante, al menos que yo noto, es que siempre tienes que estar
pendiente del tamaño de las dimensiones de los tensores que estás utilizando. Entonces
como digo, es un buen ejercicio siempre intentar adelantarte y decir, bueno, estoy esperando
una variable que tenga esta forma. En este caso, cuál es el tipo de variable que estamos
esperando. Vamos a verlo. I.shape debería ser un vector de tamaño 1000, ¿vale? ¿Qué
es lo que tenemos aquí? ¿Por qué un vector de tamaño 1000? Bueno, pues nosotros estamos
metiendo una imagen en nuestro modelo y el resultado que obtenemos por la capa de salida
es, para cada una de las clases que podría ser esa imagen, obtenemos una probabilidad.
Es decir, por ejemplo, si tenemos una clase que es estrella de mar, pues la probabilidad
asignada a esa clase nos quiere decir en qué porcentaje la red neuronal cree que en la
imagen que le hemos proporcionado hay una estrella de mar.
Por tanto, pues tiene sentido que tengamos un vector con mil probabilidades diferentes,
¿vale? ¿Qué pasa? Que si nosotros queremos ver el resultado de nuestra predicción, cuál
es el contenido de lo que hay en esta foto y vemos el vector, pues efectivamente tenemos
un vector con mil probabilidades. No sé qué esperábamos obtener, pero esto es muy complicado
de interpretar para nosotros. Es decir, aquí cómo podrías encontrar cuál es la más alta.
Tendrías que hacer un máximo de esta variable y todo esto. Afortunadamente, como el modelo
lo hemos cargado con Keras, la propia librería de Keras te proporciona funcionalidades que
pueden servirte de ayuda. Por ejemplo, en este caso, nosotros hemos importado antes esta
funcionalidad aquí que se llama Decode Predictions, que básicamente lo que hace es mapearte estas
probabilidades, este vector de probabilidades con el nombre real de las clases y además
también de las proporciones ordenadas. Es decir, te va a mostrar la clase con mayor
probabilidad. Con lo cual, si utilizamos esa función Decode Predictions, pues podemos
ver el resultado de nuestra predicción. Efectivamente, como podemos ver aquí, la primera clase que
he identificado, la primera categoría que he identificado es un vaso de cerveza, ¿no?
que es efectivamente el contenido de nuestra foto. Además, te adjunta la probabilidad
y vemos que le asigna un 20% de probabilidad a que esta foto contiene eso. Luego tenemos
también una taza de café, como en segunda posición, y Picture, que creo que también
está relacionado con algún envase, con algún contenido. Con lo cual vemos que, mira, ahí
beer bottle también, botella de cerveza. Con lo cual vemos que efectivamente la red neuronal
sabe interpretar que en esta foto pues tenemos un recipiente, en este caso son jarras de
cerveza, llena de esto de cerveza. Con lo cual podemos decir que efectivamente la red neuronal
está funcionando. Vamos a probar con la otra foto que teníamos, que es la del gatito.
Vamos a ver qué ocurre aquí si le damos, le damos a predecir de nuevo y vamos a ver
el resultado de la predicción. Bueno, yo creo que esto lo podemos poner a la celda
de arriba para ser un poquito más eficientes. Y efectivamente vemos que sí, Tabby hace
referencia a un tipo de gato. En segunda categoría también tenemos un tiger cat, que es otro
tipo de gato, egyptian cat, y luego ya remote control, que ahí sí que no. Pero como vemos
le da un 78% de probabilidad, es decir, tiene mucha certeza de que el contenido de esta
foto es este tipo de gato. Con lo cual pues podemos ver que efectivamente el modelo Inception
V3 está funcionando bien, que lo hemos podido cargar y ya ven que muy fácilmente podemos
empezar a utilizarlo. Pues efectivamente parece que funciona muy bien. ¿Qué toca ahora?
Romperlo. Para romper el modelo lo que vamos a hacer es el uso de esta técnica que estamos
haciendo en este vídeo, que son los ataques adversarios. Voy a dar de nuevo una explicación
rápida de lo que son los ataques adversarios, aunque como ya digo también en el Data Coffee
pues me extiendo y explico de qué se trata. Un ataque adversario lo que te permite es
generar un tipo de entrada de datos, un tipo de datos que tú le suministras como entrada
a la red neuronal, que hace que la red neuronal se confunda y prediga una clase diferente
de la que debería de ser la habitual. Además, lo que sucede con este tipo de ataques adversarios,
con este tipo de imágenes que generas, en el caso de esta red hablamos de imágenes,
que podrían ser sonidos, podrían ser objetos entre te, cualquier tipo de datos podría
ser motivo de un ataque adversario. Pero bueno, en este caso las imágenes, cuando tú las
observas al ojo humano tú no eres capaz de identificar que esa imagen ha sido manipulada,
que ha sido de alguna manera lo que digo manipulada para que confunda la red neuronal. Eso es
lo que hace muy interesante este tipo de ataques porque son invisibles al ojo humano. Entonces
la pregunta sería, ¿cómo podemos generar este tipo de imágenes, estas imágenes adversarias?
Como comento en el Data Coffee, este tipo de ataques son muy interesantes porque la
forma de realizarlo se asemeja bastante al proceso de aprendizaje de la red neuronal.
Es decir, tú en una red neuronal lo que tienes pues son una serie de parámetros que tú
mediante un proceso de optimización vas a ir ajustando poco a poco para que el error
de la red neuronal se minimice, ¿vale? En este caso nosotros vamos a hacer algo parecido
pero diferente. En vez de los parámetros ajustarlos para minimizar el error, en vez
de hacer ese proceso de optimización, vamos a realizar un proceso de optimización pero
sobre la imagen de entrada, ¿vale? En este caso los parámetros los dejamos fijos porque
queremos que la red neuronal siga funcionando tal cual funcionaba previamente y para eso
necesitamos que los parámetros se mantengan iguales, pero los píxeles de la imagen de
entrada van a ser nuestros nuevos parámetros y lo vamos a ir ajustando para que el error
de la red neuronal o la probabilidad para que cierta clase aparezca vaya incrementándose,
es decir, vamos a maximizarlo. Entonces eso es lo que queremos implementar en el ejercicio
de hoy. Para ello, pues lo que vamos a hacer es redefinir el grafo, es decir, nosotros
nuestra red neuronal ahora mismo en TensorFlow está definida como un grafo de diferentes
procesos que se van realizando sobre nuestro dato, ¿vale? Pues lo que vamos a hacer nosotros
es construirnos un nuevo grafo que represente este proceso de optimización que nos permita
generar las imágenes. Para eso, pues lo primero que vamos a sacar es el nodo de entrada, es
decir, el punto de entrada, la capa por donde entramos los datos y la capa por donde sacamos
los datos de nuestra red neuronal. Para eso vamos a coger y vamos a poner un input layer,
una variable input layer y vamos a coger de la variable v3 la primera capa y vamos a coger
su input, ¿vale? Es decir, estamos diciendo del iv3, cogemos la primera capa y cogemos
el punto de entrada. Y de la misma manera vamos a coger una capa de salida, outlier
le vamos a llamar y va a ser de todas las capas, vamos a coger la última, en Python
acuérdense que podemos referenciar al último elemento de un array con menos 1, vamos a
coger el output, debería de cogerlo bien, si ahora nosotros vemos que tenemos dentro
de in layer, pues deberíamos de ver que tenemos efectivamente el tensor de entrada, así es
como nos lo explica, tenemos el tensor con esta forma y de la misma manera podríamos
ver que también tenemos nuestra variable outlier. Outlier como vemos es el resultado
de una operación softmax, que es la última que se aplica con la forma que habíamos dicho,
que sería 1,000 en este caso porque estamos metiendo solamente una imagen. Ya verán que
lo que viene ahora en realidad no es muy complejo, es poquito código lo que tenemos que implementar,
pero es importante que si no están familiarizados con Keras o con tensorflow, que separen un
momento para entender exactamente por qué las líneas de código que estamos poniendo
funcionan, ¿vale? Como digo, nosotros lo que queremos hacer es un grafo que nos represente
este proceso de optimización, donde vamos a maximizar la probabilidad de que cierta
clase aparezca. Es decir, el ataque adversario que vamos a hacer es un ataque no en el que
solamente vamos a confundir a la red neuronal, es decir, que no solo vamos a hacer que se
equivoque, sino que vaya a predecir una clase que nosotros estemos buscando. Por ejemplo,
en este caso tenemos que nuestra imagen de entrada es un gatito, pero sin embargo no
queremos que sea un gato. Queremos que sea otra clase diferente dentro de las que nos
ofrece esta red neuronal. Por ejemplo, que sea un limón. Queremos que el gato lo detecte
como un limón. Pues vale, entonces sabemos que la clase limón está referenciada con
el valor 951. Pues entonces vamos a apuntar en una variable, vamos a poner target class
igual 951. Y lo que vamos a hacer es que queremos que la probabilidad de que esta clase, la
clase 951, aparezca vaya maximizándose. Es decir, que esa probabilidad cada vez sea mayor.
Con lo cual, si nosotros vamos a hacer un proceso de optimización, tenemos que decirle
cuál es el elemento que tiene que maximizar en este caso. Y lo que queremos que maximice
es la probabilidad de esa clase. Pues entonces lo que le vamos a decir es que la última
capa de nuestra red, que es la que nos da las probabilidades, vamos a hacer una función
de coste que sea que esta última capa, el resultado que nos proporciona sea target class.
Con esto lo que le estamos diciendo es que nuestra función de coste que queremos maximizar
es el resultado, la probabilidad de la clase 951. Es decir, de todo el vector de probabilidades
que nos proporciona queremos que esa sea la clase maximizada. ¿Lo entiendes más o menos?
Una vez tenemos la función de coste ya definida, pues podemos crear nuestro gradiente, ¿vale?
Como ya les digo, cuando tú quieres entrenar una red neuronal, lo que necesitas encontrar
es el gradiente entre tu error y los parámetros. Eso te va a decir con qué valores tienes
que utilizar para poco a poco ir minimizando ese error. En este caso el gradiente no lo
vamos a utilizar sobre los parámetros, sino que lo vamos a utilizar sobre la variable
de entrada que habíamos definido. Entonces en nuestro caso la variable de entrada es
input layer. Pues vamos a poner que el gradiente va a ser, vamos a utilizar Keras gradient
para que calcule directamente todo el grafo de gradientes entre el tensor input layer,
que es donde vamos a tener nuestra imagen, y la función de coste que hemos definido,
es decir, esta de aquí. Bueno, en este caso hay que ponerla al revés. Sería la función
de coste primero, es decir, el coste respecto al tensor de entrada. Con lo cual ahora mismo
en nuestro gradiente, esto hay que ponerle el índice 0 porque no puede volver un array,
en este caso ahora nuestra variable gradiente hace referencia al proceso de calcular el
gradiente entre el error y la variable de entrada. Con lo cual ya tendríamos dentro
de gradiente un tensor cuyos valores nos van a decir en qué proporción tenemos que
variar los píxeles de nuestra imagen para que poco a poco la función de coste se vaya
optimizando. Una vez tenemos definido el grafo con las operaciones, tenemos que crear una
función que va a ser la que vayamos ejecutando iterativamente para que poco a poco pues vaya
computándose esta operación del gradiente. Entonces para eso vamos a utilizar, en vez
de utilizar las herramientas que nos proporciona TensorFlow, vamos a utilizar las que nos proporciona
Keras, que yo la verdad que antes de hacer este tutorial la desconocía y me han dado
un poco quebradero de cabezas, pero bueno, ya les puedo enseñar más o menos cómo funcionan.
Entonces vamos a crear una variable que sea optimize gradient, ¿vale? Y esto de aquí
va a ser una función de tipo Kera. A este tipo de funciones lo que le tenemos que suministrar
es por un lado cuáles son los valores, la variable que le vamos a dar como entrada y
cuáles son los resultados que esperamos como salida. Entonces automáticamente pues te
va a crear una función que va a ejecutar todo el grafo necesario para realizar esta función.
Entonces en nuestro caso la variable que le vamos a dar a esta función pues va a ser
el input layer, es decir, es donde vamos a colocar nuestra imagen y lo que vamos a obtener
de salida pues va a ser el valor del gradiente, que es lo que nos interesa, es el valor que
nos va a decir cómo tenemos que manipular nuestros píxeles y también le vamos a pedir
el coste y así podemos imprimirlo. Una cosa que es lo que digo que me ha dado quebradero
de cabeza es que Kera tiene, cuando tienes un modelo tiene diferentes modos de funcionar
si estás entrenando o si estás testeando. Y eso lo tienes que definir con una variable
que es KLearningFace, ¿vale? Y que se la tienes que pasar al modelo en el momento en el que
lo ejecutas. Y eso yo no lo sabía y por eso he perdido bastante tiempo intentando hacer
este tutorial. Entonces para poder pasarle esa variable pues lo que hacemos es directamente
la ponemos también aquí como uno de los valores de entrada que tenemos que suministrar
al modelo. Muy bien, una vez tenemos esto pues lo que tenemos que hacer ahora es el
bucle iterativo donde vamos a ejecutar este código. Entonces para eso vamos a crear una
variable de coste y le vamos a decir que mientras el coste sea menor que 0,95 por ejemplo, mientras
el coste sea menor a 95% que siga iterando. ¿Y qué va a iterar? Bueno, lo que vamos
a hacer en cada una de las iteraciones va a ser ejecutar esta función que hemos implementado.
Entonces vamos a cargar la variable gr, va a ser donde guardemos el gradiente en cada
una de las operaciones, pues vamos a llamar la función optimize gradient que es la que
hemos definido aquí arriba. La hemos definido con dos valores de entrada. ¿Cuáles son?
Por una parte tenemos el input layer que en nuestro caso va a ser la imagen que queramos
optimizar como es la que hemos cargado ya anteriormente, en este caso sería la variable
xtaki que es la que tiene la imagen del gatito. Entonces le vamos a meter la imagen x, pero
para no perder los datos que ya hemos cargado dentro de la variable x, lo que vamos a hacer
es que antes vamos a hacer una copia de esta variable. Entonces vamos a crear una nueva
variable que sea adversarial a dv y aquí vamos a tener una copia de la variable x,
con lo cual aquí ahora tendríamos una copia de nuestra foto del gatito. Entonces aquí
le pasaríamos la copia de esta imagen y también le pasamos el valor del color name face y
lo ponemos a cero para referirnos a que estamos en modo de prueba, en modo testing. Con lo
cual si nosotros ejecutáramos esto ahora, pues en nuestro gradiente es donde se guardarían
esos valores de los pixeles. Con lo cual lo que tendríamos que hacer a continuación
sería sumárselos a nuestra imagen para que esos pixeles se vean manipulados. Es decir,
nosotros cogemos nuestra imagen que la tenemos guardada en adv y podemos sumarle el valor
de nuestro gradiente. También acuérdense que hemos puesto aquí arriba que el resultado
de esta función son dos variables, tenemos el gradiente y el coste, con lo cual esto
aquí debería de ser esta dos variables. Sería el gradiente lo guardaría aquí y
el coste lo guardaría aquí. Cuando recibimos el gradiente actualizamos nuestra imagen y
también lo que vamos a hacer es imprimir el valor del coste. Entonces podemos decir
target cost y el coste. Con esto ahora mismo podríamos ya ejecutarlo y podríamos empezar
a ver cómo el coste debería de irse incrementando poco a poco. ¿Por qué? Porque estamos ajustando
el valor de esta imagen, iterativamente cada iteración estamos manipulando los pixeles
de manera que nuestra función de coste se vea poco a poco maximizada. Vamos a ver si
está funcionando y si la cosa arranca. Vale, como ven empieza a ejecutarse y podemos ver
que de momento el coste asociado a la clase que queremos hacer que la red neuronal confunda
tiene un valor muy bajo pero poco a poco podemos ver como ese valor se va incrementando y según
vamos bajando pues vemos que sí, que se está incrementando y eso significa que nuestro
sistema funciona bien. Este proceso puede quedar de más o menos en tu ordenador dependiendo
del hardware que tengas en tu equipo y también dependerá de si tienes instalado la versión
de tensorflow para trabajar con GPU. Si no la tienes instalada te recomiendo encarecidamente
que la instales porque trabajar con la GPU va a hacer que todo el procesamiento de matrices
y tensores funcione mucho más rápido con lo cual pues vas a perder menos tiempo durante
todos estos procesos. Bueno, como vemos de repente el coste se aumenta exponencialmente
y al final acabamos teniendo un coste del 99% de la clase a la que queríamos aspirar
es decir esta es la clase limón de hecho vamos a llamarle lemon cost. Efectivamente
el coste asociado a la clase limón para una foto de un gato que hemos ido manipulando
poco a poco ha llegado hasta el 99% es decir ¿qué habrá pasado? ¿cómo será la manipulación
de esta imagen? Pues vamos a verla. Vamos a intentar hacer un plot de la variable adv
para eso vamos a utilizar la función image show de la librería matplotlib y vamos a
poner adv preteshow y efectivamente nota un error porque el formato, ah porque la imagen
tiene la dimensión del tensor si no me equivoco. Efectivamente no, queremos que sea simplemente
la imagen. Vale, tenemos un error diferente y en este caso el error sería que el rango
de valores no es el correcto. Vale, lo que tenemos que hacer es pasar, ¿se acuerdan que
antes hicimos el preprocesamiento para que la imagen tuviera los valores de menos 1
a 1? Pues tenemos que volver a pasar esos valores al formato de 0.255 con lo cual lo
que tenemos que hacer simplemente pues sería revertir el valor de adv. Para eso invertimos
los pasos que hicimos antes que sería dividimos entre 2, sumamos 0.5 y multiplicamos por 255.
Si no me equivoco adv ahora tendría que tener el formato correcto y también lo que tendríamos
que hacer sería cambiar el tipo de datos de nuestra variable porque actualmente la
hemos transformado de int a float y ahora queremos que de nuevo sea de float a int,
entonces tendríamos que coger y decir as type, tipo uint. Pero como no he copiado la
variable pues realmente esto me lo tengo que llevar a otra celda porque si lo volvemos
a ejecutar volvemos a procesarla y entonces se nos quedan unas intensidades, unas intensidades
mayores. Entonces lo voy a poner aquí, de hecho esta celda pues está un poco mal hecho
todo esto pero bueno. Si lo visualizamos lo que tenemos es la imagen del gato, es decir
ahora mismo esta imagen del gato si nosotros se la volviéramos a pasar al modelo detectaría
con un 99% de probabilidad que este gato es un limón. Básicamente esto sería una imagen
adversaria pero a lo mejor alguno está diciendo oye vale bueno si es cierto veo un gato veo
la imagen de un gato pero también me fijo que la imagen parece que se ha roto que tiene
como algunos píxeles con intensidades muy saturadas. Efectivamente estos valores que
vemos aquí saturados son los que el modelo ha ido reajustando durante el proceso de optimización
para que la red neuronal se vea confundida y piense que esto es un limón. Pero como
dije un buen ataque adversario tiene que ser un ataque que no sea perceptible al ojo humano
es decir que nosotros no veamos este tipo de artefactos que hagan pensar que la imagen
está manipulada. Entonces para eso lo que hay que hacer en el proceso de optimización
son dos pasos que también lo explique en el Data Coffee. Por un lado tenemos el proceso
de maximizar el error a favor de una clase determinada pero también tenemos que minimizar
la diferencia, tenemos que minimizar la perturbación entre la imagen original y la imagen que
está siendo manipulada por este proceso de optimización y eso es muy sencillito de hacer
o sea tampoco tiene mucha mayor complejitud y lo vamos a ver ahora. Básicamente lo que
tenemos que hacer es decirle a la red neuronal que cuando manipule los píxeles no se extralimite
variando, manipulando los valores. Vamos a decirle que se mantenga dentro de un rango
de valores un máximo y un mínimo que puede manipular. Ese máximo y ese mínimo, ese
umbral, esa diferencia es lo que vamos a llamar perturbación. En este caso lo vamos
a llamar Pert. Le vamos a decir que es simplemente una intensidad de 0,01, muy poquito. Podemos
tener aquí que la máxima perturbación sería el valor de la imagen X más 0,01 y la mínima
perturbación sería el valor de X menos 0,01. Estas dos variables de aquí van a representar
nuestros umbrales de los cuales si nosotros manipulamos los píxeles ninguno de los valores
de los píxeles se puede pasar, no puede ni ser ni mayor que X más 0,01 ni menor que
X menos 0,01. Vale, con lo cual una vez tenemos esto pues lo que tendríamos que hacer a continuación
pues sería, en este caso aquí cuando manipulamos los píxeles, cuando le sumamos el gradiente
a la variable ADV, a la variable adversarial que es donde tenemos la imagen manipulada,
después de esto le vamos a hacer un clip. ¿Qué significa clip? Pues básicamente los
valores mayores a esta variable los saturas en ese punto, es decir, no pueden ser mayores
a esa variable y los menores no pueden ser menores a esta variable. No recuerdo si el
orden correcto es este o es al revés, de hecho diría que es al revés. Bueno, de hecho
cuando no se acuerden cómo es el funcionamiento de una función determinada, un truco es venirte
y escribir la función y ponerle una interrogación. Ejecutas esta celda y automáticamente tienes
aquí una descripción de la función que estás utilizando. En este caso como vemos en nbclip
pues sí, efectivamente tiene un mínimo y un máximo. Y también lo estaba poniendo
mal porque antes hay que poner la variable a la cual le queremos aplicar ese clip. Con
lo cual, en este paso que estamos diciendo, vale, tú manipula la imagen como quieras,
pero no te pases ni de la mínima perturbación ni de la máxima perturbación que hemos definido
aquí arriba. Con lo cual, si ahora ejecutáramos todo este siglo de nuevo, pues no deberíamos
de ver ningún tipo de artefacto extraño. Además también otra cosa que vamos a hacer
es que, como hemos dicho, el valor de esta variable, de las intensidades tienen que estar
limitadas entre menos 1 y 1, con lo cual, pues sería correcto también poner un límite
en los valores de las intensidades. Con lo cual, ahora mismo si nosotros volviéramos
a ejecutar este código, pues tendríamos en nuestra imagen adversaria, pero sin ningún
tipo de perturbación. Vamos a verla y lo que vamos a hacer también es crear una copia
de la variable para que no se nos genere el mismo problema que antes. Vamos a llamarle
HACKET IMAGE MPCOPY DE ADV. Vamos a llamarle HACKET y vamos a ejecutarlo.
Como vemos de nuevo, el coste empieza a aumentar para la clase que hemos definido, poco a poco,
hasta que llega un punto en el que llega al máximo del proceso de optimización, que
sería el 99%. Y en este caso podemos ver de nuevo la foto, que sí, como podemos ver
en este caso, no tiene ningún tipo de artefacto que nos haga pensar que esta foto ha sido
manipulada. Como ven, esta que tenemos aquí abajo es la que teníamos antes, y ahora mismo
la que tenemos sería esta de aquí. No hay ningún pixel que nos haga pensar que esta
imagen tiene algún tipo de distorsión. De hecho, si cogemos esta imagen que acabamos
de generar y la guardamos en nuestro ordenador, para eso vamos a utilizar el módulo IMAGE
y vamos a guardar la imagen a partir del array que tenemos generado, es decir, de ADV0 ASType
BU8, y esto lo guardamos en nuestro directorio, vamos a llamarle HACKET.PNG. Vale, si esto
lo guardamos, ahora mismo tendríamos en nuestro ordenador, a ver si aparece, ah vale, yo lo
tengo en el directorio de arriba, vamos a solucionarlo, sería este, así, y así. Vale,
si lo guardamos ahora en nuestro directorio podemos ver que tenemos las dos imágenes,
la del cató original y la que hemos manipulado con nuestro ataque adversario. Vamos a comprobar
si nuestra red ahora es capaz de predecir que esta imagen ha sido hackeada o no. Vamos
a ver qué pasa si le metemos la imagen a la cual hemos manipulado y que no podemos
identificar como si fuera una manipulación. Vamos a ver qué resultado nota nuestra red.
Como ven, hemos simplemente cambiado la ruta por la nueva imagen que hemos creado, que
en este caso es.png, y si la ejecutamos, efectivamente nuestro gato ahora de repente
con un 99,9528% de probabilidades predice la red que es un limón. Si a lo mejor se equivoca
en ese porcentaje, la segunda opción más probable es que sea una naranja o un pepino,
y ya luego tenemos como cuarta y quinta opción una pelota de tenis o una banana. Como ven,
hemos conseguido que la red neuronal se confunda en el resultado, le hemos llevado a creer
que efectivamente esa foto del gato con un 99% de probabilidad es otra cosa completamente
diferente, un limón, y también podemos ver que en ninguna de las opciones no se cuestiona
en ningún momento que en esa foto hay información que le haga pensar que es un gato. Aún así,
cuando vemos la foto original, podemos ver que efectivamente para nosotros sigue siendo
un gato y no hay nada que nos indique que ha habido una manipulación. Esto sería un
ataque adversario, sería un ejemplo de cómo realizar un ataque adversario, y como ven
es súper sencillo de construir si tienes acceso al propio modelo, y es bastante bastante
efectivo, como hemos podido comprobar. Un detalle que quería comentar y que a lo mejor les
puede estar pasando es que nosotros hemos manipulado nuestra imagen a una pequeña variación
a nivel de píxeles, y lo que detecté es que por ejemplo el ataque que hemos hecho en este
caso, la manera en la que lo hemos hecho, pues no es muy robusto, es decir, a la mínima
manipulación que tú produzcas en esos píxeles, nuevamente puede ser que todo el ataque se
nos desmonte y que la red neuronal vuelva a detectar que lo que hay en la imagen es
un gato. De hecho, eso me estaba pasando a mí, porque yo guardaba siempre las imágenes
en formato.jpg, y eso hace que la calidad de la imagen baja un poco y por tanto las
intensidades de los píxeles también varíen, y cuando probaba a ver si el modelo podía
detectar o no que había en la foto, siempre me daba que detectaba un gato, es decir, que
el ataque adversario no se había producido, y estuve un tiempo buscando hasta que me di
cuenta que efectivamente si lo guardabas en png, como lo hemos guardado ahora, pues el
ataque de adversario se producía. Y bueno, nada más, esto era el ejemplo que les quería
traer hoy, espero que les haya gustado, que lo hayan entendido bien. Como ya he dicho,
esto es un poco más complejo, es más nivel del que tenía pensado inicialmente para
este tipo de vídeos, pero bueno, iremos variando según el contenido que vayamos viendo en
el canal y según vea también vuestro feedback, que me tenéis que dejar abajo aquí en la
zona de comentarios que está justo aquí debajo mía. Así que eso, dejad todas vuestras
opiniones, si les ha gustado el vídeo dejen un like, un comentario también apoyando el
canal, siempre viene bien, compartan por redes sociales para que el proyecto poco a poco
siga creciendo cada vez más, y también tenemos disponible un patreon para aquellos que quieran
aportar una cantidad económica y quieran apoyar el canal financieramente, pues también
está esa opción ahí, tienen el enlace a la descripción de este vídeo. Quiero agradecer
a mis dos patreons que tengo actualmente, uno Junior González, que está aportando
una cantidad mensual, y también a Laura, que creo que se incorporó recientemente o
el mes pasado, y son mis dos primeros patreons en el canal, lo cual me hace mucha ilusión.
Y bueno, nada más, hay más por venir, así que estad atentos y nos vemos en el próximo
vídeo. Adiós.