Autoencoders para compresión de imágenes
Autoencoders para compresión de imágenes
Resumen
Se estudia la viabilidad del uso de autoencoders en la compresión de imágenes para
escenarios de la vida real usando un método de división por cuadrícula de la imagen
original. Se construyen varios modelos, utilizando diferentes técnicas de entrenamiento,
y se compara su calidad de reconstrucción con obtenida por otros formatos de compre-
sión con pérdida establecidos en la industria, como JPEG, en su similitud con la imagen
original.
Para ello, se desarrollan compresores basados en un autoencoder con su correspon-
dientes descompresores. La validación se hace con 41 imágenes de una base de datos
de imágenes públicas, las cuales se comprimen y reconstruyen, para entonces extraer las
métricas de similitud con las imágenes originales mse, psnr y ssim. El objetivo es optimi-
zar las métricas obtenidas por el compresor desarrollado y comprarlas con las métricas
obtenidas por otros formatos de compresión establecidos en la industria.
Palabras clave: compresión de imagen; descompresión; red neuronal; autoencoder
Abstract
In this work the main focus will be to study viability of the use of autencoders in
the task of image compression, making use of a grid division of the image. The per-
formance of the autoencoder-based compressor will be evaluated and compared with
other industry-stablished formats, such as JPEG, designing neuronal network models
and training them with various techniques.
To achieve this an autoencoder-based compressor is developed, with its correspond-
ing decompressor. The validation of its performance is performed with 41 images with
different content types, all of them retrieved from a public image database, with which
the reconstrucction fidelity is evaluated using the mse, psnr and ssim metrics. The objec-
tive is to optimize optimize these metrics for the autoencoder-based compressor, while
keeping the compression ratio as low as possible. Then, the results are compared with
the results produced by industry-stablished image compression formats.
Key words: image compression, image decompression, neuronal net, autoencoder
III
Índice general
Índice general V
Índice de figuras VII
Índice de tablas VIII
1 Introducción 1
1.1 Motivación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Estructura de la memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Estado de la cuestión 3
2.1 Algoritmos de compresión sin pérdida . . . . . . . . . . . . . . . . . . . . . 3
2.2 Algoritmos de compresión con pérdida . . . . . . . . . . . . . . . . . . . . 4
3 Marco Teórico 7
3.1 Redes neuronales artificiales . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.1.1 Funciones de activación . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.1.2 Función de pérdida . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.1.3 Entrenamiento de redes . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.1.4 Optimizadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.5 Topologías de Redes Neuronales . . . . . . . . . . . . . . . . . . . . 13
3.2 Autoencoders para la reducción de dimensionalidad . . . . . . . . . . . . . 15
3.2.1 Otras aplicaciones de los autoencoders . . . . . . . . . . . . . . . . . 16
3.3 Técnicas de aumento de datos en imágenes . . . . . . . . . . . . . . . . . . 16
3.3.1 Tipos de aumentos de datos en imágenes . . . . . . . . . . . . . . . 17
3.4 Compresión PNG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.5 Compresión JPEG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.6 Modelos de representación del color . . . . . . . . . . . . . . . . . . . . . . 26
3.6.1 RGB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.6.2 YCbCr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.7 Métricas de calidad de imágenes . . . . . . . . . . . . . . . . . . . . . . . . 27
3.7.1 Error medio cuadrático - MSE . . . . . . . . . . . . . . . . . . . . . . 27
3.7.2 Proporción máxima de señal a ruido - PSNR . . . . . . . . . . . . . 28
3.7.3 Similitud estructural - SSIM . . . . . . . . . . . . . . . . . . . . . . . 28
4 Metodología empleada 29
4.1 Herramientas empleadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1.1 Librerías de Python empleadas . . . . . . . . . . . . . . . . . . . . . 30
4.2 Algoritmo propuesto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.3 Otras herramientas creadas para el trabajo . . . . . . . . . . . . . . . . . . . 32
4.3.1 Generación de lotes de bloques para el entrenamiento . . . . . . . . 32
4.3.2 Entrenamiento de modelos . . . . . . . . . . . . . . . . . . . . . . . 34
4.3.3 Diseño del compresor . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.3.4 Diseño del descompresor . . . . . . . . . . . . . . . . . . . . . . . . 38
4.3.5 Estructura de un autoencoder . . . . . . . . . . . . . . . . . . . . . . . 39
5 Experimentación y resultados 41
V
VI ÍNDICE GENERAL
5.1 Error de entrenamiento de los modelos usando Leaky ReLU, ReLU y Sigmoid 42
5.2 Valores de MSE, PSNR y SSIM por cada función de activación . . . . . . . 43
5.3 Comparación de cómo diferentes profundidades de modelo y anchos de
representaciones afectan al PNSR de la reconstrucción . . . . . . . . . . . . 44
5.4 Modelos representados como grafos . . . . . . . . . . . . . . . . . . . . . . 45
5.5 MSE, PSNR y SSIM de los modelos entrenados comparados con JPEG . . . 46
5.6 Resultados de aplicar las diferentes técnicas y JPEG como comparación
visual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.7 MSE, PSNR y SSIM de los modelos entrenados con ruido comparados con
JPEG y los modelos entrenados sin ruido . . . . . . . . . . . . . . . . . . . 49
VII
VIII ÍNDICE DE TABLAS
Índice de tablas
3.1 Tipos de filtros usados por PNG en la fase de filtrado como son explicados
en la recomendación del World Wide Web Consortium [2] . . . . . . . . . 20
Introducción
En esta sección se explican los diferentes motivos que motivan el desarrollo del tra-
bajo, los objetivos que finalmente se pretenden conseguir y cuál es la estructura de la
memoria en su conjunto.
1.1 Motivación
1.2 Objetivos
Este trabajo pretende, como principal objetivo, estudiar la viabilidad del uso de los
autoencoders como motor en un compresor de imágenes. Como objetivos concretos para
llevar a cabo el objetivo principal se determinan los siguientes pasos:
1
2 Introducción
3. Medir la calidad de reconstrucción obtenida por cada uno de los niveles de com-
presión usando las métricas MSE, PSNR y SSIM.
4. Comparar los resultados con los obtenidos por JPEG, determinando así si el uso de
autoencoders como motor para los compresores es viable
La memoria de este trabajo está dividida en siete capítulos, junto con un anexo.
6. Conclusiones. Explica las conclusiones finales de este trabajo y mejoras que se po-
drían aplicar en futuros trabajos.
8. Anexo ODS. Explica el grado de relación de este trabajo con los objetivos de desa-
rrollo sostenible de las Naciones Unidas.
CAPÍTULO 2
Estado de la cuestión
3
4 Estado de la cuestión
Figura 2.1: Proporciones de compresión de algoritmos sin pérdida, incluyendo PNG. Imagen ex-
traída de [31].
comprimen imágenes con contenidos variados y tamaños diferentes con los compresores
que se estudian.
Los resultados de este estudio se pueden ver en la figura 2.1. En dicha figura se com-
paran la proporción de compresión de JPEG 2000, JPEG XR, JPEG XL, JPEG-LS, JPEG-XT,
PNG y WebP en imágenes de tamaños entre 683x1024 y 6016x4000. Se puede observar
que el algoritmo de compresión JPEG XL es el que obtiene mejores resultados de forma
consistente, mientras que PNG obtiene los peores resultados.
Figura 2.2: PNSR medio contra proporción de compresión de JPEG y JPEG200 [11]
do en el eje x una proporción de compresión en vez de bits por píxel (bpp). En todos los
casos JPEG 2000 obtiene los mejores resultados, con una puntuación de PNSR mayor.
CAPÍTULO 3
Marco Teórico
En esta sección se introducen todos los conocimientos teóricos que han sido necesa-
rios para el desarrollo de este proyecto. Entre ellos se encuentran las redes neuronales
artificiales, que son la forma en la que crean los autoencoders, cómo se entrenan y todos
los componentes que las crean. Además, se explican otros conocimientos necesarios para
el tratamiento de imágenes, como los modelos de representación del color, los algoritmos
de compresión de PNG y JPEG y las métricas de similitud entre imágenes. Finalmente,
se hace una introducción a la reducción de dimensionalidad y se explica cómo funciona
en un autoencoder.
Las redes neuronales artificiales, o ANN de sus siglas en inglés, son un método de
aprendizaje inspirado en el funcionamiento de las neuronas de un cerebro biológico. Es-
tas funcionan como una función matemática muy compleja, que es capaz de adaptarse a
los datos que se le proporcionan.
La unidad básica de una red neuronal es la neurona. Esta se representa por un nodo
con un sesgo (B) y un peso (W). Estas neuronas se organizan en capas, que recogen va-
lores (X) y utilizan su parámetros para calcular nuevas representaciones (Y). Estas capas
pueden entenderse como operaciones matriciales con la siguiente forma:
Y = X×W +B
Estas capas a su vez pueden ser ordenadas de forma que el resultado de una alimente
a las siguientes, proporcionándole una estructura de red, muy similar a las redes neuro-
7
8 Marco Teórico
nales biológicas. Estas redes pueden ser de diferentes profundidades, dependiendo del
número de capas que la formen, y cada una de estas capas pueden tener diferentes an-
churas. Del uso de redes neuronales profundas es de donde recibe el nombre el «Apren-
dizaje Profundo» o Deep Learning. La estructura de red se puede ver fácilmente cuando
estas funciones matemáticas son representadas como grafos, como en la Figura 3.1.
Sin embargo, combinar capas de esta forma, con operaciones lineales una tras otra, re-
sulta en otra operación lineal. Aunque una operación lineal se puede usar para clasificar,
esta no nos proporciona una expresividad suficiente. Para incrementar esta expresividad
y conseguir que las redes neuronales no sean funciones lineales se intercalan las capas
lineales con funciones no lineales, llamadas funciones de activación.
Finalmente, en la capa de salida, se recogen los resultados de esta función. Depen-
diendo del problema el número de neuronas que tiene esta capa de salida, es decir su
ancho, puede variar. En problemas de clasificación suele usarse como ancho de salida el
número de clases entre las que se quiera clasificar, mientras que en problemas de regre-
sión se utilizan tantas dimensiones como el problema requiera.
Las funciones de activación son el secreto que hace que las redes neuronales sean
capaces de resolver problemas complejos. Estas se añaden después de cada capa lineal,
para evitar el comportamiento lineal en todos los pasos. Además de no ser operaciones
lineales, las funciones de activación deben tener, preferiblemente, una primera derivada
que sea fácilmente computable. La razón para esto quedará evidente en la sección 3.1.3.
Existen muchas funciones de activación, cada una con sus propiedades. En esta sec-
ción se explicarán algunas de ellas.
Sigmoid es otra función de activación que era muy utilizada en los primeros mode-
los de redes neuronales. Una gráfica de Sigmoid se puede ver en la figura 3.2b. Esta
1
f ( x) = σ( x) =
1 + e− x
f ′ ( x ) = σ( x )(1 − σ( x ))
El campo de las funciones de activación es uno que todavía está abierto, sobre el
que se sigue investigando y publicando. Otras funciones que se utilizan a menudo, y
que obtienen diferentes resultados, son Exponential Linear Unit, Swish Function, Softmax,
Parametrized ReLU y más.
Las funciones de pérdida son la forma de medir cómo de cerca está la predicción
hecha por la red neuronal del valor objetivo. En cierta forma es la manera de calcular
la distancia que hay entre estos dos valores. Intuitivamente, una mayor pérdida indica
peores predicciones o un peor rendimiento; de esa forma sabemos como el modelo que
se está entrenando debe mejorar. Muchas veces el tipo de función de pérdida que se use
depende del tipo del problema. Algunas de las funciones de pérdida son:
Problemas de regresión
Error absoluto o Error L1. Mide el error absoluto entre el valor de la predicción y
el objetivo. A diferencia de MSE no es tan susceptible a valores atípicos, ya que
crece linealmente. Sin embargo, la pendiente de L1 en el valor objetivo, es decir en
el valor del dominio cero, no es nula, ya que esta es constante a ambos lados de este
punto en el dominio. En otras palabras, L1 no es derivable en todo su dominio, ya
que no tiene pendiente en el cero.
Problemas de clasificación
Cross Entropy Loss es una función de pérdida para clasificación de múltiples clases.
Esta utiliza la propiedad de la teoría de la información que dice que algo más proba-
ble de conseguir proporciona menos información para definir la siguiente fórmula:
Igual que con las funciones de activación, las funciones de pérdida son un campo de
investigación y del que todavía se publican artículos. Otras funciones de pérdida para
problemas de clasificación son Exponential Loss, Hinge Loss, Pinball Loss y Rescaled Hinge
Loss, mientras que para problemas de regresión existen Huber Loss, Log-cosh Loss y Quan-
tile Loss [28]
Una vez la arquitectura de una red neuronal ha sido decidida, las funciones de ac-
tivación están en su lugar y la función de pérdida preparada, solo queda entrenarla. El
entrenamiento de una red nueronal es, en esencia, la optimización de su función mate-
mática respecto a la función de pérdida. Sin embargo, calcular en qué punto la función
de pérdida tiene un valor menor es muy complicado de manera analítica, ya que esta
tiene innumerables mínimos locales. Para ello, el entrenamiento se hace a partir de una
algoritmo de descenso de gradiente.
Para llevar a cabo el descenso de gradiente es necesaria una gran cantidad de datos,
que se separan en tres conjuntos: el conjunto de entrenamiento, validación y testing. El
entrenamiento se realiza únicamente a partir del conjunto de entrenamiento, y es del
único de los tres conjuntos del que aprenderá una red neuronal. Este conjunto suele tomar
entre el 60 y 80 por ciento de los datos, aunque depende mucho de la cantidad de datos
que se tenga.
El objetivo es conseguir que los parámetros de una red neuronal, los pesos y sesgos de
cada una de las neuronas, encuentren el valor óptimo para que la función de pérdida dé el
valor mínimo posible, en el mayor número de casos posibles. Además, estos parámetros
no deberán ajustarse únicamente al conjunto de entrenamiento, sino que deberán hacerlo
también al conjunto de validación.
Para conseguir esto, se necesita de una cantidad de datos en cuanto más extensa po-
sible mejor. Además, se pueden usar técnicas de regularización, como añadir un término
extra a la función de pérdida, para que la red neuronal sea capaz de extrapolar sus apren-
dizajes al mayor número de casos no previamente contemplados como sea posible.
La parte más importante durante el entrenamiento, sin embargo, son dos fases lla-
madas «propagaciones». Estas reciben sus nombre por la propagación de los resultados
3.1 Redes neuronales artificiales 11
Figura 3.3: Una tasa de aprendizaje muy grande (izquierda) puede hacer que el descenso de gra-
diente nunca converja, mientras que una tasa de aprendizaje muy pequeña (derecha) puede hacer
que esta converja muy lentamente [9]
primero hacia delante, de entrada a salida, y luego hacia atrás, del error hacia la entrada,
pasando por todos los parámetros. Estos dos procesos se llaman:
dx dx dt
=
dy dt dy
3.1.4. Optimizadores
(a) Descenso de pendiente. ([12] Capítulo 4.3) (b) Descenso de pendiente estocástico más mo-
mento. ([12] Capítulo 8.3)
Figura 3.4: Se muestra cómo diferentes algoritmos de optimización se acercan al mínimo global.
Descenso de pendiente
θ k +1 = θ k − α · ∆ θ L
v k +1 = β · v k − α · ∆ θ L ( θ k )
θ k +1 = θ k + v k +1
v k +1 = β · v k + (1 − β ) · ∆ θ L ( θ k )
sk+1 = µ · sk + (1 − µ) · [∆θ L(θ k ) ◦ ∆θ L(θ k )]
v k +1
θ k +1 = θ k + α · √
s k +1 + ϵ
Las redes neuronales artificiales ayudan a resolver una variedad problemas muy dife-
rentes y cada vez se buscan nuevas formas de expandir la utilidad de estas herramientas.
Sin embargo, diseñar modelos para que se ajusten lo mejor posible a nuestro problema es
una tarea difícil. Aquí se explican algunas de los tipos de redes neuronales que se utilizan
o se han utilizado para resolver diferentes problemas.
Las FFN son el tipo de red neuronal más clásicas. Estas, como se explica en la sec-
ción 3.1, se construyen apilando capas ocultas de neuronas comúnmente llamadas capas
«Totalmente conectadas», o fully connected, e intercalándolas con capas de activación. Las
capas fully connected pasarán todos sus resultados a cada una de las neuronas de la si-
guiente capa. Apilando más y más capas se consiguen construir modelos más complejos,
que le dan una mayor potencia de clasificación o regresión a los modelos. Una ilustración
de una red FNN se puede ver en la Figura 3.1.
14 Marco Teórico
Es lógico llegar a la conclusión de que una red neuronal más profunda siempre será
mejor, pero esto sería incorrecto, ya que unos problemas muy comunes en las redes neu-
ronales demasiado profundas son los de los gradientes que se desvanecen y gradientes
explosivos. Para aplacar estos problemas, se introducen las conexiones de salto. La idea
de estas conexiones es darle un camino más directo a los datos, realimentando las capas
más profundas con datos de capas anteriores. De esta forma, el proceso de entrenamiento
es más rápido y efectivo.
Las RNN son una familia de redes neuronales para procesar datos secuenciales. Es-
tas guardan los resultados intermedios de pases anteriores para calcular el resultado del
pase actual. De esta forma, pueden usar lo que han aprendido en los pases anteriores y
aplicarlo en el actual. Una arquitectura en concreto de esta familia son los Long Short Term
Memory o LSTM, que recogen los datos de múltiples pases anteriores en vez de solo uno,
como hacen las RNN convencionales. Esto es, en esencia, otorgarle una memoria a corto
y largo plazo a la red neuronal, en vez de solo corto plazo como en las RNN.
Autoencoder Networks
Los autoencoders pueden verse como un caso especial de FFN, que es entrenada co-
piando su entrada a la salida. De entre sus capas ocultas, una de ellas describe un código
que representa la entrada. Históricamente los autoencoders se han usado la reducción de
dimensionalidad, obteniendo un código h que ocupa menos espacio que los datos origi-
nales, aplicación que se le da en este trabajo también.
Este código h es también llamado un espacio latente. El espacio latente contiene una
representación comprimida de la entrada, recogiendo determinada información, que será
la única información que el decodificador podrá usar para reconstruir la entrada en la sa-
lida con la mayor lealtad posible. Más información sobre esta familia de redes neuronales
en la sección 3.2.
Las redes neuronales convolucionales son un tipo de FFN que aprovecha la estructura
de los datos aplicando operaciones matemáticas llamadas convoluciones. Esta técnica se
usa satisfactoriamente en problemas de reconocimiento de formas en imágenes, procesa-
miento del lenguaje natural y otros procesos de análisis de imágenes. Una ventaja muy
importante de las CNN es que, al reemplazar las capas totalmente conectadas de las FFN
por capas convolucionales, se reduce sustancialmente el número de parámetros que se
debe entrenar.
Un GAN cuenta con dos partes un generador y un discriminador. Estas dos partes
trabajan como adversarios, los generadores son entrenados para que sean capaces de si-
mular datos originales, mientras que los discriminadores son entrenados para diferenciar
entre datos originales y simulados. Esta familia de redes neuronales entran dentro de la
categoría de aprendizaje no supervisado, ya que nuevos datos pueden ser generados a
partir de los datos de entrada.
3.2 Autoencoders para la reducción de dimensionalidad 15
Figura 3.5: Estructura visual de un autoencoder para la compresión de imágenes. Imagen obtenida
en [5].
De entre las aplicaciones de los autoencoders, una de las más comunes es la reducción
de la dimensionalidad de los datos. Normalmente copiar la entrada en la salida puede
parecer algo bastante inútil; sin embargo, con este proceso se consigue que las capas in-
termedias sean capaces de recoger información interesante sobre la muestra. Estas capas
intermedias, también llamadas espacios latentes, son diferentes representaciones de los
datos que se introducen. Una manera de recoger esta información es teniendo una di-
mensión inferior en las capas intermedias a la de la entrada, usando un cuello de botella.
Una reducción de la dimensionalidad implica que se pierde información por el ca-
mino, que estos datos ya no están completos. Por ello, este tipo de autoencoders recibe el
apellido de «Incompletos». El proceso que sigue un autoencoder es muy similar al de otros
algoritmos de reducción de dimensionalidad, como PCA. De hecho, si utilizáramos una
sola capa oculta y MSE como función de pérdida, sería totalmente equivalente a PCA y
aprendería a proyectar el conjunto de entrenamiento al mismo subespacio.
Sin embargo, gracias a la arquitectura modular de una red neuronal FFN, de la que
deriva la arquitectura autoencoder, podemos hacer uso de funciones no lineales, interca-
lándolas entre las capas lineales. Así se consigue una transformación generalizada no
lineal más potente que la de PCA, que se ajusta mejor a los datos de entrenamiento. Esto,
a su vez, puede resultar en un problema ya que un autoencoder con demasiada libertad
puede aprender que un código i representa una salida x (i) , sin haber extraido información
relevante, y obtener malos resultados en casos no vistos durante el entrenamiento.
Al final, un autoencoder se compone de dos partes: una función codificadora f y una
función decodificadora g, como se puede ver en la figura 3.5. El codificador extrae la in-
formación relevante, aplicando su función no lineal a los datos, y con esa información
la función decodificadora proyecta al espacio original, tomando la entrada del codifica-
dor y aplicando una nueva proyección no lineal sobre ella. Estas funciones deben usarse
siempre juntas.
En el entrenamiento, como se quiere minimizar el error de reconstrucción, el objetivo
es minimizar la función de pérdida que, dada una entrada x, se define de la siguiente
forma:
L( x, g( f ( x )))
de la entrada en un espacio latente. En cuanto mayor sea el ancho del cuello de botella
y más dimensiones tenga para representar sus propiedades, mayor será la capacidad de
recoger información del codificador. En otras palabras, un espacio latente de mayor di-
mensionalidad será capaz de recoger más información de la muestra y, por lo tanto, el
decodificador podrá reconstruir la muestra más fielmente.
Además, durante el entrenamiento, el decodificador aprende a reconocer la informa-
ción que recibe del codificador y a reconstruir los datos originales. Este lenguaje en el que
se comunican es único entre estas dos partes, ya que no todos los codificadores obtendrán
el mismo resultado y ningún otro decodificador será capaz de entender el espacio latente
resultante de este codificador, a no ser que sea entrenado expresamente con este.
Denoising autoencoders
Obtener los datos apropiados es una de las partes más importantes del proceso de
entrenamiento. Esto es en ocasiones complicado. Obtener suficientes datos para que las
redes que se entrenan sean capaces de generalizar a casos nuevos, que no han sido vistos
previamente durante el entrenamiento, es en ocasiones difícil e incluso imposible. Para
superar estos problemas se han desarrollado diferentes métodos de «aumento de datos».
El aumento de datos es una forma de, a partir de un conjunto de datos de entrena-
miento, conseguir un conjunto de datos mayor, es decir aumentar la cantidad de datos
3.3 Técnicas de aumento de datos en imágenes 17
Figura 3.6: Ejemplos de aumento de datos usando crop y reflejos extraídos de [19]
de la que se dispone. Estos procesos aplican muchas veces modificaciones a los datos de
entrenamiento, de diversas formas, para obtener nuevos datos basados en los originales.
En el caso del aumento de datos para imágenes, estas técnicas pueden tener muchas
formas, pero, en todos los casos, tratan de obtener imágenes nuevas a partir de las imá-
genes de entrenamiento originales. Estas técnicas pueden ser aplicadas individualmente,
en conjunto y en diversas proporciones para obtener diferentes resultados.
Modificaciones geométricas
Las transformaciones geométricas son muy útiles cuando existe un sesgo en la geo-
metría de los datos de entrenamiento. Los sesgos en el conjunto de entrenamiento pueden
aparecer de muchas formas, pero si este este viene de forma geométrica, como cuando el
objeto buscado está siempre en la misma postura o en la misma posición relativa dentro
de la imagen, las transformaciones geométricas pueden ser una manera muy buena de
obtener mejores generalizaciones.
Un ejemplo de transformación geométrica pude ser el reflejo. Reflejar una imagen es
bastante fácil de entender. A partir de la imagen original se obtiene una nueva en la que
se invierte uno de los ejes. De esta forma, dependiendo de las orientaciones en las que se
aplique el reflejo, se pueden obtener hasta cuatro versiones, que, a efectos prácticos, es
multiplicar por cuatro el tamaño del conjunto de entrenamiento. Se pueden ver ejemplos
de cómo se vería esto en una imagen en las figuras 3.6a y 3.6b.
18 Marco Teórico
(b) Imágenes con ruido del tipo Salt and Pepper, Oculsión, y Poisson
Figura 3.7: Ejemplos de aumento de datos usando inyección de ruido aleatorio extraídos de [3]
Cropping o recortes
El cropping consiste en, a partir de una imagen de dimensiones HxW, obtener una o
varias imágenes recortando los píxeles de la imagen original. En todos los casos la imagen
resultante tiene una dimensión AxB que es inferior o igual a la de la imagen original.
Este tipo de aumento de datos se puede utilizar para sustituir un cambio de resolución
de la imagen original, una alternativa que se habría utilizado para reducir el tamaño
de la entrada. Sin embargo, a diferencia de un cambio de resolución que solo tiene un
posible resultado, el cropping puede obtener diferentes resultados, dependiendo de cómo
se configure.
Algunos métodos establecidos son el de recortar cinco piezas de forma que se recortan
las esquinas y el centro o recortar de forma aleatoria. Todos estos métodos son alterna-
tivas que se usan en la práctica y con los que se han visto resultados positivos [22]. Un
ejemplo de cómo se vería el recorte de la imágenes de forma aleatoria se puede ver en la
figura 3.6c.
Cuando el sesgo que existe en los datos de entrenamiento no es del tipo geométrico y
es más complejo que este, la inclusión de ruido en las imágenes es una manera de hacer
que el modelo aprenda a generalizar de forma muy efectiva. Este ruido se puede aplicar
3.4 Compresión PNG 19
Filtrado o Predicción
Tipo Proceso
Ninguno Devuelve los valores igual
Resta Devuelve la diferencia entre cada byte y el byte del píxel a la izquierda
Arriba Como en la resta pero con el píxel superior en vez de el de la izquierda
Media Usa la media de los píxeles superior y anterior para calcular la diferencia
Paeth Utiliza los tres píxeles más cercanos para calcular una función lineal
Tabla 3.1: Tipos de filtros usados por PNG en la fase de filtrado como son explicados en la reco-
mendación del World Wide Web Consortium [2]
donde los datos originales v se representan como la diferencia entre los valores consecu-
tivos en v̂.
El objetivo de este proceso es conseguir el mayor número de valores duplicados co-
mo sea posible, ya que este tipo de datos es mucho más fácil de comprimir. Es decir, se
espera que, a partir de los datos de los canales de las imágenes, se encuentre alguna co-
rrelación lineal que incremente de forma constante, para que luego el compresor tenga
una mayor eficiencia. Este proceso se aplica por canal de color, lo que permite al formato
PNG amoldarse a diferentes espacios de color como la inclusión del canal alpha para la
transparencia de manera sencilla.
En el formato PNG, el primer paso del filtrado consiste en seccionar la imagen por
filas, a las que se les aplicará uno de los cinco filtros que este tiene en su especificación.
El motivo por el que PNG tiene diferentes filtros es para conseguir el mayor número
de datos repetidos. Algunos de ellos hacen uso de los datos de la capa inmediatamente
superior para conseguir desvelar correlaciones lineales más complejas. Los cinco tipos de
filtros se pueden ver en la tabla 3.1.
El filtro que se elija para cada fila se aplicará a todos los canales de la misma forma,
pero a cada canal individualmente. De esta forma, la nueva representación de cada fila
vendrá dada por el tipo de filtro que se haya utilizado en esa fila, representado con un
valor de 0 a 4, seguido por los datos filtrados de cada uno de los canales.
La forma en la que se elije el filtro que se usa en cada una de las filas es un proce-
so complicado. Aunque podría parecer que probar todos los filtros y escoger el que da
mejores resultados tras la compresión sería la mejor forma, este proceso de fuerza bruta
es demasiado costoso. En vez de esto muchas implementaciones de PNG utilizan unas
reglas predefinidas, que intentan pronosticar cuál de los filtros es el óptimo. Estas reglas
se construyen a partir de la experimentación con muchos tipos de imagen.
Compresión DEFLATE
El siguiente paso, una vez los datos han sido filtrados, es el de la compresión. Para
este proceso se utiliza un algoritmo parecido al algoritmo de compresión Lempel-Ziv com-
pression, llamado DEFLATE. Este se puede dividir en dos partes: una que elimina cadenas
duplicadas y otra que hace uso de códigos de Huffman para sustituir los símbolos más
utilizados por representaciones más cortas y los menos utilizados por representaciones
más largas.
3.4 Compresión PNG 21
Volvía a ser de noche. En la posada Roca de Guía reinaba el silencio, un silencio triple.
↓
Volvía a ser de noche. En la posada Roca de Guía reinaba el silencio, un(14,9) triple.
donde la cadena « silencio» puede ser sustituida por una representación más corta,
especificando el número de caracteres que se debe retroceder (14) y el número de carac-
teres que constituyen esta cadenas (9). En el caso de PNG, como este trabaja con bytes en
vez de caracteres, se cuenta el número de bytes que se debe retroceder con la distancia y
el número de bytes que componen la cadena.
Para señalizar la referencia, PNG utiliza 8 bits para la longitud y 15 para la distancia.
En total eso hace que se pueda apuntar a una cadena de píxeles a una distancia de, como
máximo, 32,768 bytes y con una longitud mínima de tres bytes y 258 como máximo.
Este algoritmo es realmente la parte que más cuesta de computar de la compresión, sin
embargo, es posible reducir la ventana en la que se comparan las cadenas duplicadas para
reducir este tiempo a cambio de una menor compresión, aunque igualmente compatible.
PNG utiliza una ventana máxima de 32768 bytes [2].
Una propiedad interesante, aunque no intuitiva, de este proceso es que representar
varias repeticiones de una misma cadena es muy sencillo. Para ello, se puede utilizar una
referencia con una longitud más larga que la distancia de la referencia. Así se vería un
ejemplo de una repetición en unos versos de una canción [18]:
When are these colonies gonna rise up? When are these colonies gonna rise up? When are these
colonies gonna rise up? When are these colonies gonna rise up?
↓
When are these colonies gonna rise up? (39, 117)
Descomprimir PNG
Para la descompresión de la imagen se aplican los procesos opuestos, ya que son com-
pletamente reversibles. Primero, a partir de la cadena de códigos Huffman y su corres-
pondiente árbol, se reconstruye la cadena resultante de la compresión LZSS. Se utilizan
los códigos de referencias para reconstruir las filas por canal resultante del filtrado. Como
el filtrado añade un código para indicar qué tipo de filtrado se aplicó a dicha cadena, se
puede aplicar el proceso inverso para recuperar los datos originales, reconstruyendo así
la imagen original.
22 Marco Teórico
Para conseguir este propósito JPEG utiliza un espacio de color diferente a RGB, YCb Cr .
Al utilizar este espacio de color se puede separar la intensidad de la luz de los compo-
nentes de croma (color), permitiendo aplicarle un tratamiento diferente a cada uno de
los canales. Para encontrar más información acerca de espacios de color y de cómo se
transforma de RGB a YCb Cr se puede leer la sección 3.6.
Reducción de resolución
El primer paso de compresión que se aplica es una reducción de resolución a los ca-
nales de croma Cb y Cr . El nivel de la reducción de resolución puede ser cambiado para
producir diferentes resultados y existen diferentes proporciones: por ejemplo 4:4:4, que
no aplica ninguna reducción de resolución, 4:2:2, que aplica la reducción con dos píxeles
adyacentes horizontalmente, y 4:2:0, que reduce la resolución vertical y horizontalmente.
Reduciendo la resolución de esta forma es posible reducir el tamaño de los datos de ma-
nera muy efectiva mientras que se pierde relativamente poca información. Por ejemplo,
3.5 Compresión JPEG 23
la configuración de compresión más utilizada, que es 4:2:0, consigue una reducción del
50 % mientras que se pierde relativamente poca información, al menos par el ojo humano.
Además, cabe tener en cuenta que esta reducción de resolución se puede aplicar de
diferentes formas. Las formas más comunes son downsampling, el cual escoge el color del
píxel haciendo la media de los elegidos, y subsampling, que, como se muestra en la figura
3.10, escoge el valor del píxel en la esquina superior izquierda.
Cabe destacar, que a partir de este paso todos los demás también se aplican de for-
ma separada a cada uno de los canales de color. Además, la imagen también se separa
en una cuadrícula de bloques de 8x8, a los que se les aplica los siguientes pasos inde-
pendientemente. Cuando una de las dimensiones de la imagen, o ambas, no es divisible
por este factor de ocho, el resto de la cuadrícula se rellena con datos arbitrarios, aunque
una práctica común es la de repetir los píxeles, para evitar producir imperfecciones en el
resultado final.
En este apartado se tratan los bloques de imagen como señales. Al aplicar DCT se
convierte cada bloque de una serie de amplitudes a una serie de frecuencias de funciones
de coseno. Para ello, primero se centran los datos a cero, de forma que en vez de tomar
valores en el rango [0,255] estén en el rango [-128,127]. A la matriz resultante se le aplica
la transformación de DCT de dos dimensiones adaptada a los bloques 8x8:
7 7
1 uπ 1 vπ 1
Gu,v = α(u)α(v) ∑ ∑ gx,y cos x+ cos y+
4 x =0 y =0 8 2 8 2
donde
Tras esta transformación la esquina superior izquierda de la matriz Gu,v tendrá siem-
pre un valor mucho más alto que los demás, mientras que los otros valores serán mucho
más pequeños, algunos muy cercanos al cero. Esto es debido a la tendencia de DCT de
trasladar la mayoría de los pesos a esa esquina. Luego el proceso de cuantificación acen-
tuará más este efecto.
Cuantificación digital
Tras el paso de DCT, la matriz resultante cuenta con un 64 números de coma flotante.
Representar estos datos requiere más espacio de lo que se usaba antes para representar
64 enteros en el rango [0,255]. Para solucionar eso lo que se hace ahora es cuantificar los
valores, dividiendo cada uno de ellos por una constante y convirtiéndolos a enteros.
El bloque resultante de DCT es dividido elemento por elemento con una matriz de
cuantificación Q, que puede ser cambiada para obtener calidades de compresión diferen-
tes. Como la mayoría de los valores con magnitud grande están agrupados en la esquina
superior izquierda de la matriz Gu,v , la matriz de cuantificación toma valores más peque-
ños en esa misma esquina, para reducir menos su magnitud y valores más grandes en el
resto de esquinas para obtener valores más cercanos a cero.
Finalmente, todos los valores de la matriz resultante son redondeados al entero más
cercano, obteniendo así la matriz Cu,v . En esencia, la operación de cuantificación se puede
resumir en la siguiente expresión:
Gu,v
Cu,v =
Qu,v
Codificación de entropía
donde el primer valor, -26 en este caso, no se comprime de la misma forma y el resto
de tuplas tienen tres valores [RUNLENGTH,TAMAÑO,AMPLITUD]. Estos tres valores
tienen los siguientes significados:
3.5 Compresión JPEG 25
Entonces, igual que en PNG, JPEG codifica el patrón generado usando codificación
de Huffman. El estándar de JPEG proporciona una tablas de Huffman para construir los
árboles, aunque el codificador puede elegir construir sus propias tablas a partir de los
patrones obtenidos. Este sería el resultado final del archivo JPEG y lo que se guardaría en
memoria.
Descompresión JPEG
Para descomprimir un archivo JPEG se tienen que seguir los pasos de forma inversa,
como se muestra en la figura 3.9. La codificación de entropía es totalmente sin pérdi-
da, por lo que se puede recuperar la representación anterior sin problema. Con ello, se
reconstruye el bloque y se deshace la cuantificación, multiplicando los valores por los
coeficientes de cuantificación.
El siguiente paso es deshacer el DCT con una DCT inversa bidimensional para recu-
perar las amplitudes originales en el rango [-128,127]. Esto se hace aplicando la siguiente
ecuación:
1 7 7
uπ 1 vπ 1
gx,y = ∑ ∑ α(u)α(v) Gu,v cos x+ cos y+
4 u =0 v =0 8 2 8 2
a las que luego se les suma 128 para recuperar el rango de valores [0,255]. Finalmente,
se aumenta la resolución de los canales de croma que hayan pasado por una reducción
de resolución y se recupera el espacio de color RGB.
(c) Representación del componente verde (d) Representación del componente azul
Figura 3.11: Reproducción de una imagen digital como es originalmente y separada en sus com-
ponentes según el modelo RGB
Una imagen es una representación ordenada de píxeles, donde cada uno de estos pí-
xeles tiene un color asociado. Determinar qué color tiene cada píxel se hace mediante un
modelo de representación de color. Un modelo de representación del color es un modelo
matemático que describe la forma en la que un color se representa a partir de una tupla
de números de tres o cuatro valores. Además, cada modelo especifica como cada uno de
estos valores debe ser interpretado, creando lo que se llama un espacio de color. Estos
modelos suelen tener en cuenta cuál es la forma en la que los humanos perciben el color
y cuál es la mejor forma de imitar los procesos biológicos.
3.6.1. RGB
El modelo RGB es un modelo de representación del color aditivo. En este, tres com-
ponentes (rojo, verde, azul) son representados en una tupla, de forma que la mezcla de
las intensidades de estos tres colores representa un único color. Se puede ver un desglo-
se de una imagen separada en sus componentes en la figura 3.11. La forma en la que se
representa cada uno de estos componentes puede variar, aunque en el ámbito de la re-
presentación de imágenes digitales se suele usar un byte por componente, con un total de
256 valores posibles diferentes, lo que resulta en un total de 224 colores.
Una variación de RGB en la representación de imágenes introduce un cuarto compo-
nente llamado alpha, que representa la transparencia de dicho color. La adición de este
nuevo componente resulta en el espacio de color RGBA, comúnmente usado en la repre-
sentación de imágenes digitales.
Un problema que tiene el modelo de color RGB es que, aunque define el espacio de
color, no se define la forma en la que cada valor debe reproducirse. Esto implica que cada
dispositivo puede mostrar colores ligeramente diferentes, convirtiendo al modelo RGB
en un modelo dependiente del dispositivo usado.
3.7 Métricas de calidad de imágenes 27
3.6.2. YCbCr
Este es otro modelo de representación de color aditivo, pero que intenta acercarse un
poco más a la forma en la que los humanos vemos, dándole uno de los tres parámetros
de su tupla a la intensidad de la luz (Y). La razón de que se use este formato de represen-
tación del color es que al comprimir las imágenes se puede aplicar un proceso diferente
a cada uno de los canales y, como el ojo humano es más sensible a la luz que al color, se
le puede aplicar una mayor compresión a los dos componentes de color (CbCr).
YCbCr se define a partir de una transformación lineal al espacio de color RGB. La
transformación es la siguiente:
Y 0,2126 0,7152 0,0722 R
Cb = −0,1146 −0,3854 0,5 G
Cr 0,5 −0,4542 −0,0458 B
R 1 0 1,5748 Y
G = 1 −0,1873 −0,4681 Cb
B 1 1,8556 0 Cr
Una imagen tiene muchas propiedades que se pueden tener en cuenta a la hora de de-
terminar cómo de similares son dos de ellas. Tamaño, histograma de colores y formas son
algunas de las propiedades que se pueden usar en la comparación de dichas imágenes.
Sin embargo, métricas tan generales son una mala aproximación de su similitud.
Otra cosa a tener en cuenta de la imágenes es que estas tienen una estructura ordena-
da. Una imagen es una matriz de tuplas de un espacio de color y la posición dentro de
esta matriz es igual o más importante, en cuanto a la similitud, que el color exacto que se
represente. En las métricas de similitud de imágenes más utilizadas en la comparación de
algoritmos de compresión de imágenes con pérdida esta propiedad se explota en mayor
o menor medida, pero siempre se tiene en cuenta.
Existen también métodos de similitud de imágenes que utilizan el contenido de las
imágenes para determinar cuan parecidas son. Estas se pueden usar para determinar
qué dos camisetas se parecen más dadas las imágenes de ellas, entre otras cosas. Estas
medidas de similitud pueden ser conseguidas utilizando técnicas de extracción de infor-
mación explicadas en las secciones 3.1 y 3.2, pero no es el enfoque que se le da en este
estudio.
Existen muchos tipos de métricas de calidad de imagen. Algunas, como MSE y PSNR
pertenecen a las métricas de fidelidad objetiva, mientras que otras, basadas en el sistema
de percepción humano como SSIM, pertenecen a la categoría de métricas de fidelidad
subjetiva [25]. En total existen muchas más métricas, pero a día de hoy ninguna se ha
considerado óptima.
El error medio cuadrático o Mean Square Error en inglés, calcula la distancia euclídea
media de todos los píxeles de la imagen. Esta métrica es muy simple y fácil de calcular,
reduciendo la carga computacional. Un valor menor de MSE se considera mejor, ya que
28 Marco Teórico
indica una menor distancia entre los valores de la imagen original y la distorsionada.
Dadas dos imágenes A y B de dimensiones m × n esta métrica se calcula con la siguiente
formula:
1 m −1 n −1
mn i∑ ∑ [aij − bij ]2
MSE =
=0 j =0
MAX 2
PSNR = 10 log10 ( )
MSE
= 20 log10 ( MAX ) − 10 log10 ( MSE)
(2µ x µy + c1 )(2σxy + c2 )
SSI M ( x, y) =
(µ2x
+ µ2y − c1 )(σx2 + σy2 + c2 )
Donde las regiones x y y de las imágenes tienen una media de valores µ x y µy , una
varianza σx2 y σy2 y una covarianza σxy . Además, la constante c1 se define como c1 = (k1 L)2
y la constante c2 se define como c2 = (k2 L)2 , donde L es el rango de valores de un píxel
(255 por defecto), k1 = 0,01 y k2 = 0,03 (ambos por defecto también) [29].
CAPÍTULO 4
Metodología empleada
En esta sección se explica cuales son los métodos aplicados en el trabajo para llevarlo a
cabo. Se hace una descripción de todos los materiales usados, lenguajes de programación,
librerías, el editor de código y otros programas auxiliares. Seguidamente, se explican las
herramientas usadas para el entrenamiento de los modelos de autoencoder usados en los
compresores y el significado de cada uno de los parámetros usados en la configuración
de dichos autoencoders.
Para el desarrollo de este proyecto se ha utilizado una serie de herramientas, todas ne-
cesarias para que este sea completado. En esta sección se detallan todas las herramientas
que se utilizan, las razones por las que se han elegido y los usos que se les ha dado.
El lenguaje de programación elegido para el desarrollo es Python, en concreto la ver-
sión 3.9.13 de Pyhton. Python es un lenguaje sencillo de aprender, de usar para el desa-
rrollo y con innumerables librerías dedicadas. Además, existe una gran comunidad, con
lo que es fácil encontrar ayuda sobre problemas o dudas que se puedan tener, porque
probablemente alguien ya haya tenido ese problema alguna vez.
Una de las razones concretas por las que se ha usado Python, y la de más peso, es
la existencia de librerías específicas para Machine Learning. Existen varias librerías que
se pueden usar para el desarrollo en Deep Learning, como Keras y TensorFlow, pero en
concreto en este proyecto se hace uso de PyTorch.
Una vez el lenguaje de programación ha sido elegido es necesario instalar el intér-
prete, que nos permitirá ejecutar los programas escritos en nuestro sistema Windows,
aunque también existen versiones para Linux y MacOs. Esto se puede hacer de varias
formas: desde la tienda de Microsoft Store, descargando el instalador de fuera de línea
desde la página web oficial o usando un entorno como Anaconda, por ejemplo. Existen
otras formas de utilizar Python, pero en este caso se hizo uso de la tienda de Microsoft
Store para descargar la última versión disponible de Python 3.9.
Para el entrenamiento de los modelos es necesaria una gran cantidad de ejemplos.
Un gran dataset nos dará una mayor cantidad de ejemplos desde donde el modelo puede
aprender, adquiriendo mayor precisión de reconstrucción. Un dataset muy común en la
comunidad científica para la clasificación de imágenes es ImageNet [8].
Sin embargo, debido a su gran tamaño, más de 14 millones de imágenes a fecha de Ju-
nio de 2022, se opta por usar una versión reducida de esta ImageNet-r. Este es un conjunto
de imágenes etiquetadas con etiquetas ImageNet, obtenidas mediante la recopilación de
29
30 Metodología empleada
arte, dibujos animados, devianart, graffiti, bordados, gráficos, origami, pintura, patrones,
objetos de plástico, peluches, esculturas, bocetos tatuajes, juguetes y videojuegos de las
clases de ImageNet. En total tiene representaciones para 200 clases de ImageNet, resultan-
do en 30.000 imágenes [15].
El editor elegido para escribir los scripts ha sido Visual Studio Code, VS Code para
abreviar. Este editor se ha elegido por su capacidad de instalar paquetes que, entre otras
cosas, resaltan la sintaxis de Python, ayudan completando automáticamente pequeñas
partes de código como variables o nombres de funciones y su integración con las libretas
interactivas de Jupyter Notebooks y el kit de herramienas TensorBoard.
Jupyter Notebooks es un formato de documento de texto basado en JSON, capaz de
incluir código, texto narrativo, ecuaciones y texto enriquecido. Además, su diseño flexible
nos permite configurar y reorganizar la forma de trabajo y está pensando para ser usado,
entre otras cosas, en aplicaciones de Machine Learning. Por otro lado, VS Code cuenta con
un paquete para el uso y visualización de TensorBoard, que también se usará. Este es un kit
de herramientas de visualización originalmente de Tensorflow, pero que tiene integración
con PyTorch.
Todas las pruebas y el desarrollo del trabajo se han hecho desde un equipo Windows
10 con un procesador Intel i7 con velocidad base de 2.30 GHz y 4 núcleos, 16 GB de RAM
y una GPU NVIDIA Quadro P520. Esta GPU cuenta con la plataforma de computación
CUDA, que acelera la velocidad de cálculo paralelizando partes del proceso de entrena-
miento.
CUDA son las siglas en inglés de Arquitectura Unificada de Dispositivos de Cómpu-
to, que hace referencia a una plataforma de computación en paralelo, incluyendo un com-
pilador y un conjunto de herramientas de desarrollo creadas por Nvidia, que permiten a
los programadores usar una variación del lenguaje de programación C (CUDA C) para
codificar algoritmos en GPU de Nvidia[1]. A través de wrappers es posible utilizar estas
librerías en Python, por lo que librerías como PyTorch pueden hacer uso de CUDA cuando
está disponible.
Seguidamente se detallan las librerías de Python que se han utilizado, cómo instalar-
las y el uso que se les ha dado. Todas estas librerías han sido necesarias para el desarrollo
del trabajo y sin ellas habría sido difícil o imposible completar alguna de sus partes. Sin
embargo, en muchos casos Python cuenta con alternativas que tendrán la misma funcio-
nalidad y podrían reemplazarlas.
NumPy
NumPy es una librería de Python para el cálculo numérico y el análisis de datos, es-
pecialmente para un gran volumen de datos [14, 4]. En este trabajo se usa para la mani-
pulación de la imágenes una vez han sido cargadas de disco, troceando la imagen una
cuadrícula y en el proceso inverso. La librería se instala con el siguiente comando.
1 pip3 i n s t a l l numpy
PyTorch
PyTorch es la librería que se usa para el diseño y entrenamiento de los autoencoders.
Esta proporciona un marco de desarrollo para Machine Learning, con un entorno de desa-
rrollo rápido y flexible [19]. Como se ha explicado en la sección 4.1 existen otras librerías
que pueden cumplir las mismas funcionalidades con la misma eficiencia. Además, se
hace uso también de la librería Torchvision, una parte del proyecto de PyTorch, que nos
da acceso a una base de datos de datasets, arquitecturas de modelos y transformaciones
comunes para imágenes.
Instalar la versión correcta de estos paquete es muy importante, en este caso se usó la
versión 1.10.0+cu113 de PyTorch y la versión 0.11.1+cu113 de Torchvision. Estas versiones
cuentan con la capacidad de usar el kit de herramientas de CUDA 11.3. Para instalarlas
se ejecuta el siguiente comando en la terminal PowerShell:
1 pip3 i n s t a l l t o r c h = = 1 . 1 0 . 0 + cu113 t o r c h v i s i o n = = 0 . 1 1 . 1 + cu113 − f h t t p s :// download
. pytorch . org/whl/ t o r c h _ s t a b l e . html
scikit-image
Scikit-Image es un librería de algoritmos para el procesamiento de imágenes en Python
[27]. Esta librería será usada para el preprocesamiento de las imágenes en algunos de los
experimentos. La instalación de la librería desde la terminal se hace de la siguiente forma:
1 pip3 i n s t a l l s c i k i t −image
Por lo tanto el resultado de la codificación de uno de estos bloques sería una cadena de
valores enteros. Seguidamente, se cuantifican estos valores, obteniendo el rango máxi-
mo que estos ocupan para reconstruirlos más tarde y escalándolos a enteros en el rango
[0,255], para que puedan ser almacenados en un byte cada uno.
Finalmente, estas cadenas de valores se comprimen con el algoritmo de compresión
sin pérdida DEFLATE. Este paso está inspirado en algoritmo PNG, y se aplica a forma de
recoger los patrones que se repiten en los datos y reducir el tamaño de su representación.
Para descomprimir estos datos se hacen los pasos inversos. Primero, se aplica la des-
compresión de DEFLATE, obteniendo las cadenas que representan cada uno de los blo-
ques. Dichas cadenas, entonces, se introducen en la parte decidificadora del autoencoder,
recuperando los bloques en una representación similar a la original. Finalmente, dichos
bloques se reestructuran para formar la reconstrucción de la imagen original.
Además de las herramientas de terceros, se han creado otras herramientas para llevar
a cabo el desarrollo del trabajo. Estas herramientas están basadas en las librerías mencio-
nadas en las sección 4.1 y ayudan en el desarrollo del trabajo simplificando tareas.
Estas herramientas se usan específicamente para la creación de los lotes o batches de
bloques de las imágenes, usados durante el entrenamiento, y para el entrenamiento en sí
de los modelos. Además, conforme las necesidades de los experimentos cambian se han
desarrollado alternativas que también se explican en esta sección.
Para entrenar cada uno de los autoencoders usados en el compresor, se extraen de las
imágenes bloques cuadrados del tamaño deseado en cada caso. Para ello, se usan las
imágenes del conjunto de entrenamiento de ImageNet-r.
Primero, se genera una lista de todos las rutas de las imágenes contenidas en el con-
junto de entrenamiento de ImageNet-r. Para ello, se itera por todas las clases contenidas
dentro del conjunto de entrenamiento, ya que la clase de cada imagen no es realmente im-
portante, solo su contenido. Esta lista de rutas se guarda con el nombre i m a g e _ p a t h _ l i s t .
4.3 Otras herramientas creadas para el trabajo 33
Figura 4.2: Ocho bloques de tamaño 8x8 píxeles tomados de 8 imágenes aleatorias del dataset
Imagenet-r
2. t i l e _ s i z e : tamaño del lado del cuadrado que tienen los bloques a extraer. Por de-
fecto este tamaño es de ocho píxeles.
Cada vez que se llama a la función next ( batch_maker ) , esta devuelve una lista de
ocho bloques, cada uno de una imagen diferente, ya que n u m _ t i l e s está configurado a
uno. Podemos ver un ejemplo de los bloques que esta función devuelve en la figura 4.2.
Una propiedad muy interesante que tienen los generadores en Python es su capacidad
de ser iterados por un bucle, inicialmente infinito. Esta propiedad es lo que se usa en la
siguiente sección para entrenar los modelos.
Durante la experimentación, también se hará uso de una versión de g e t _ t i l e _ b a t c h
capaz de obtener dos copias de los bloques. Esta función ligeramente modificada, lla-
mada g e t _ n o i s y _ t i l e _ b a t c h , se usa de la misma forma que g e t _ t i l e _ b a t c h y devuelve,
junto a los bloques de las imágenes, una versión que tiene ruido, para entrenar los mode-
los usando estos bloques.
34 Metodología empleada
La cabecera del bucle carga un nuevo lote de bloques (tiles) que se usan para el
entrenamiento. Además, se obtiene el número del tole en el que nos encontramos en
batch_number .
1 t i l e s = t i l e s . to ( device )
Entonces, se mandan los bloques al dispositivo indicado, siendo este la GPU para una
mayor velocidad de computación. Si la GPU no está disponible se mandará a la CPU.
1 o p t i m i z e r . zero_grad ( )
Además, se reinician las pendientes de los parámetros del modelo a cero. Esto se hace
porque PyTorch acumula estas pendientes en sucesivos pases hacia atrás.
1 outputs = model ( t i l e s )
2 t r a i n _ l o s s = c r i t e r i o n ( outputs , t i l e s . view ( − 1 , 3 * t i l e _ s i z e * t i l e _ s i z e )
3 t r a i n _ l o s s . backward ( )
4 optimizer . step ( )
5 l o s s += t r a i n _ l o s s . item ( )
Seguidamente, se hace el pase hacia delante, dando como resultado outputs , que se-
rían los bloques reconstruidos. Se calcula el error de reconstrucción con la función de
pérdida c r i t e r i o n . Antes de ello, sin embargo, debemos transformar la matriz de datos
de los bloques para que tenga la misma estructura que en la salida. Para ello, hacemos
uso de la función t i l e s . view ( − 1 , 3 * t i l e _ s i z e * t i l e _ s i z e ) , que reorganiza los datos
para que tengan una estructura de ( image_number × n u m _ t i l e s ) por (3 × 8 × 8), es decir,
lado por lado por canales de la imagen RGB.
4.3 Otras herramientas creadas para el trabajo 35
Una vez t r a i n _ l o s s ha sido calculado, se hace el pase hacia atrás con backward ( ) ,
que calcula las pendientes acumuladas. Con las pendientes calculadas, o p t i m i z e r . s t e p
( ) actualiza los parámetros del modelo.
Listing 4.7: Pases hacia delante y hacia atrás para entrenar con ruido
Para automatizar el proceso de prueba se crea una clase de Python para que se en-
cargue de los pasos de separación en bloques, cálculo de la compresión del autoencoder
y cuantificación. La estructura del compresor está formada por dos partes: la función de
compresión y la de descompresión. Cada una de estas partes toma una mitad del autoen-
coder entrenado, el compresor toma el codificador y el descompresor toma el decodifica-
dor. Dichos codificadores y decodificadores deberán provenir del mismo autoencoder, ya
que estos han sido entrenados en tándem, y los parámetros dependen el uno del otro.
El compresor es un objeto del tipo Compressor_Decompressor , nombre recibido por su
naturaleza dual. Este objeto se inicializa con los siguientes parámetros:
2. model_type : clase del modelo de autoencoder que se ha usado. Diferentes clases tie-
nen diferentes estructuras que se adaptan a los parámetros cargados del modelo.
3. chunk_size (opcional): tamaño del lado del bloque que se usará en la segmentación
de la imagen. Por defecto chunk_size es ocho.
El primer paso que toma el compresor es determinar si debe usar una imagen que ya esté
cargada en memoria o si la debe cargar él. Si el parámetro image_np no es del tipo ndarray
usará image_path para cargar la imagen. Esta será cargada en un array de NumPy, con tres
canales de color RGB por defecto.
1 i ma g e _s ize = image_np . shape
2 tile_list_np = s e l f . segment_image ( image_np , pad_type= ’ r e f l e c t ’ )
3 tile_list_tensor = s e l f . make_tensor ( t i l e _ l i s t _ n p )
4 tile_list_tensor_cuda = s e l f . send_image_to_device ( t i l e _ l i s t _ t e n s o r )
5 compressed_image_tensor = s e l f . apply_compress_function ( t i l e _ l i s t _ t e n s o r _ c u d a )
6 clean_c = compressed_image_tensor . detach ( ) . cpu ( )
Otro paso previo necesario para usar el autoencoder es transformar la matriz de NumPy
a un objeto Tensor de Pytorch. Esto se hace en la función make_tensor , que cambia el orden
4.3 Otras herramientas creadas para el trabajo 37
Figura 4.3: Representación en un gráfico de caja y bigotes de los valores del espacio latente una
vez ha sido comprimida una imagen
Figura 4.4: Representación en un histograma con 20 grupos de mismo tamaño del error de recons-
trucción del espacio latente tras la decuantifiación
El descompresor, en esencia, hace el proceso inverso que el compresor. Este se usa con
el mismo autoencoder que en el compresor, porque estos están entrenado para trabajar
en conjunto. La descompresión se hace con la función decompress_image , que toma los
siguientes parámetros:
1 compressed_image = d e s c a l e _ a r r a y ( compressed_image , i n t e r v a l )
La clase AutoEncoder es la clase con la cual crearemos los modelos a entrenar. Esta
clase hereda de la clase Module de la librería t o r c h . nn. Para ello, primero se importa la
librería en la cabecera. Además, se importa también el módulo t o r c h , para acceder a más
funcionalidades que este aporta.
1 import t o r c h . nn as nn
2 import t o r c h
3
4 c l a s s AutoEncoder ( nn . Module ) :
3. h i d d e n _ s i z e s (opcional): define el número de nodos que utiliza cada una de las ca-
pas delautoencoder. Si se quiere hacer un modelo con mayor profundidad se le pue-
de dar una lista de mayor profundidad. Por defecto h i d d e n _ s i z e s es asignada una
lista con los valores [96, 8, 96]. Esta lista debe tener una longitud impar y siempre se
40 Metodología empleada
toma el valor central como el tamaño de salida una vez se calcule su representación
comprimida, mientras el parámetro compressed_size no esté habilitado.
Durante la inicialización, AutoEncoder inicializa las capas lineales para que tengan
las dimensiones que se han indicado en h i d d e n _ s i z e s y compressed_size . Además, se
asigna la función de activación. Esto se hace desde la función i n i t _ l a y e r s , de forma
que es sencillo de cambiar por cualquier otro modelo que extienda este, mejorando la
modularidad. Por último, si CUDA está disponible, el modelo se manda al dispositivo.
1 def _ _ i n i t _ _ ( s e l f , a c t i v a t i o n =nn . ReLU ( ) , i n p u t _ s i z e = 3 * 8 * 8 ,
2 h i d d e n _ s i z e s = [ 3 2 * 3 , 8 , 3 2 * 3 ] , compressed_size=None ) :
3
4 super ( AutoEncoder , s e l f ) . _ _ i n i t _ _ ( )
5 s e l f . input_size = input_size
6 i f compressed_size ! = None :
7 h i d d e n _ s i z e s [ i n t ( l e n ( h i d d e n _ s i z e s ) /2) ] = compressed_size
8
9 s e l f . i n i t _ l a y e r s ( activation , hidden_sizes )
10
11 d e v i c e = t o r c h . d e v i c e ( " cuda : 0 " i f t o r c h . cuda . i s _ a v a i l a b l e ( ) e l s e " cpu " )
12 s e l f . to ( device )
Una vez el resultado de la función definida por el modelo ha sido calculado, se calcula
el error mediante la función de pérdida, como es explicado en la sección 4.3.2 y luego se
hace el pase hacia atrás, para calcular los gradientes. En PyTorch, no es necesario definir
un método de pase hacia atrás, ya que el propio marco lo infiere a partir del pase hacia
delante, la definido en formward.
Otros métodos que se han creado para el manejo más sencillo de estos modelos son
save_model y load_model , que son simples funciones para guardar y cargar de disco los
modelos. save_model es principalmente usado una vez el modelo ha sido entrenado,
mientras que load_model es usado por Compressor_Decompressor para luego aplicar las
funciones de compresión y descompresión de dicho modelo en las imágenes.
1 def save_model ( s e l f ,PATH) :
2 t o r c h . save ( s e l f . s t a t e _ d i c t ( ) ,PATH)
3
4 def load_model (PATH, compression_out ) :
5 model = AutoEncoder ( h i d d e n _ s i z e s = [ 3 2 * 3 , compression_out , 3 2 * 3 ] )
6 model . l o a d _ s t a t e _ d i c t ( t o r c h . load (PATH) )
7 model . e v a l ( )
8 r e t u r n model
Experimentación y resultados
Para encontrar cuales son las características que hacen que un autoencoder comprima
y reconstruya una imagen con el menor error posible, se han hecho una serie de ex-
perimentos. En esta sección se detallan todos los experimentos realizados y cuál es su
implementación.
Cada experimento consiste en cambiar un parámetro o característica de la red, y se
quiere medir cuánto afecta este cambio a la calidad de reconstrucción. Para mantener
una rigurosidad lo mayor posible, en todos los experimentos se usan algunas configu-
raciones que no se cambiaron en ningún experimento, salvo cuando se especifica. Las
configuraciones que se mantienen constantes son las siguientes:
5. La imagen se utiliza en todos casos con el modelo de color RGB y los canales de
color no son independientes, estando conectados entre sí.
6. Cada uno de los modelos se prueba finalmente con las mismas imágenes, una co-
lección de 41 imágenes de todo tipo extraídas de la base de datos del SIPI [24],
incluyendo retratos, imágenes satélite, paisajes, escenas de películas en blanco y
negro y más. Para cada modelo se calcula el MSE, PSNR y SSIM con cada imagen,
con lo que luego se calcula una media.
7. Los resultados a veces se comparan con los resultados de JPEG obtenidos calcu-
lando las mismas métricas que las utilizadas sobre imágenes comprimidas con el
compresor JPEG de la aplicación web https://squoosh.app. Los resultados obteni-
dos de dichas pruebas pueden verse en la tabla 5.1.
41
42 Experimentación y resultados
Figura 5.1: Error de entrenamiento de los modelos usando Leaky ReLU, ReLU y Sigmoid
Figura 5.2: Valores de MSE, PSNR y SSIM por cada función de activación
Como se explica en la sección 3.1, una profundidad mayor en las redes neuronales
del tipo FNN otorga una mayor expresividad y una mayor capacidad de aprendizaje
de características. En este experimento, se pretende estudiar cual es el impacto de este
cambio de profundidad en la calidad de compresión y reconstrucción.
Para ello, se diseñaron cuatro autoencoders con diferentes profundidades, de entre 4 y
8 capas ocultas. La profundidad de dichos autoencoders se mide en el número de capas
ocultas que este tiene, siendo estas las capas que tienen parámetros ajustables. En este
experimento se utilizan únicamente capas totalmente conectadas, intercaladas con capas
de activación ReLU.
La implementación de los modelos se hace mediante un modelo base, con el nombre
de clase AutoEncoder , preparado para alojar cuatro capas lineales, inicializadas estas en
la función i n i t _ l a y e r s que, como se explica en la sección 4.3.5, es llamada en la inicia-
lización de una instancia nueva del modelo. La inicialización de las capas se hace de la
siguiente forma:
1 self . activation = activation
2 #Encode
3 s e l f . f c 1 = nn . L i n e a r ( s e l f . i n p u t _ s i z e , hidden_sizes [ 0 ] )
4 s e l f . f c 2 = nn . L i n e a r ( h i d d e n _ s i z e s [ 0 ] , hidden_sizes [ 1 ] )
5 #Decode
6 s e l f . f c 3 = nn . L i n e a r ( h i d d e n _ s i z e s [ 1 ] , hidden_sizes [ 2 ] )
7 s e l f . f c 4 = nn . L i n e a r ( h i d d e n _ s i z e s [ 2 ] , s e l f . input_size )
Luego, durante la evaluación del modelo, la función forward llama a las funciones
encode y decode , que utilizan las capas lineales inicializadas bajo el comentario de Encode
y Decode respectivamente. También se intercalan capas de activación del tipo ReLU.
Este mismo proceso de inicialización se usa en el resto de modelos AutoEncoder_6H ,
AutoEncoder_8H , AutoEncoder_12H . Sin embargo, en el resto se inicializan, en vez de cuatro
44 Experimentación y resultados
Tabla 5.2: Tabla de los anchos de las capas utilizadas y el número de capas ocultas en cada una de
las arquitecturas de autoencoder con las que se experimentó.
(b) Autoencoder_6H
(a) Autoencoder
(c) Autoencoder_8H
(d) Autoencoder_12H
(a) PSNR
(b) MSE
(c) SSIM
Figura 5.5: MSE, PSNR y SSIM de los modelos entrenados comparados con JPEG
5.3 Denoising autoencoders 47
Tabla 5.3: D: profundidad en número de capas ocultas, H: ancho en bytes, Tamaño: tamaño total
tras compresión en autoencoder, DEFLATE: tamaño tras compresión con DEFLATE
Este experimento pretende entrenar modelos utilizando como entrada una versión
del bloque que deben reconstruir a la que se le ha añadido ruido. La intención es entrenar
modelos más robustos, que sean capaces de generalizar mejor a casos de imágenes no
vistas durante el entrenamiento.
Para completar este experimento, se utiliza ruido del tipo Salt and Pepper ya que se
ha encontrado que funciona bien en la aumento de datos para otros problemas de visión
por computador [17]. De esta forma, durante el entrenamiento el modelo debe intentar
Tabla 5.4: H: ancho en bytes, Tamaño: tamaño total tras compresión en un autoencoder entrenado
con ruido, DEFLATE: tamaño tras compresión con DEFLATE
48 Experimentación y resultados
Figura 5.6: Resultados de aplicar las diferentes técnicas y JPEG como comparación visual
reconstruir el bloque sin el ruido que se da como entrada. Al incluir este nuevo ruido a la
entrada, la función de pérdida se define como
N
L= ∑ ( g( f (xi + SnP)) − xi )2
i =1
donde SnP indica el ruido que se añade a cada uno de los bloques.
Para implementar este ruido en la entrada se hace uso de la función de scikit-image
random_noise . Esta función se encuentra en el apartado de skimage . u t i l s y se usa de la
siguiente forma:
1 noisy_image = random_noise ( image_np , mode= " s&p " , amount = 0 . 3 )
De esta forma se indica que se quiere añadir ruido aleatorio del tipo Salt and Pepper,
mode= " s&p " ; con una probabilidad del 30 por ciento, amount = 0 . 3 ; en la imagen image_np .
Esta nos dará una imagen igual que la introducida, pero con un ruido añadido. Segui-
damente, se extraen de las mismas coordenadas bloques aleatorios en ambas imágenes,
como se explica en la sección 4.3.1.
Con esta nueva forma de construir los datos de entrenamiento se entrenan tres mo-
delos diferentes con diferentes anchos de cuello de botella. Para los tres modelos se usa
una arquitectura con ocho capas ocultas de la clase AutoEncoder_8H con ReLU como fun-
ción de activación. Estos tres modelos tienen anchos de 8, 12 y 14 bytes. Una vez son
entrenados se prueban en el conjunto de imágenes de prueba y se obtienen los resultados
observados en la tabla 5.4 y la figura 5.7, además de un ejemplo visual en la figura 5.6.
Los resultados muestran que, a pesar de que se esperaba que añadirle ruido a la entra-
da fuera a dar mejores resultados, la adición de ruido no es del todo efectivo. Es posible
que no se haya añadido suficiente ruido o que, en cambio, se haya añadido demasiado.
En cualquier caso, el modelo entrenado sin ruido tiene mejores métricas y JPEG todavía
mejores.
5.3 Denoising autoencoders 49
(a) PSNR
(b) MSE
(c) SSIM
Figura 5.7: MSE, PSNR y SSIM de los modelos entrenados con ruido comparados con JPEG y los
modelos entrenados sin ruido
CAPÍTULO 6
Conclusión
51
52 Conclusión
[1] Fedy Abi-Chahla. Nvidia’s cuda: The end of the cpu? https://www.tomshardware.
com/reviews/nvidia-cuda-gpu,1954.html, 2008. [Internet; descargado 3-junio-
2022].
[3] Murtaza Eren Akbiyik. Data augmentation in training cnns: Injecting noise to ima-
ges. ICLR, 2019.
[5] Dor Bank, Noam Koenigstein, and Raja Giryes. Autoencoders, 2020.
[6] Zhengxue Cheng, Heming Sun, Masaru Takeuchi, and Jiro Katto. Deep convolutio-
nal autoencoder-based lossy image compression. CoRR, abs/1804.09535, 2018.
[8] Jia Deng, Wei Dong, Richard Socher, Li-Jia Li, Kai Li, and Li Fei-Fei. Imagenet: A
large-scale hierarchical image database. In 2009 IEEE conference on computer vision
and pattern recognition, pages 248–255. Ieee, 2009.
[11] Farzad Ebrahimi, Matthieu Chamik, and Stefan Winkler. Jpeg vs. jpeg 2000: an objec-
tive comparison of image encoding quality. In Applications of Digital Image Processing
XXVII, volume 5558, pages 300–308. SPIE, 2004.
[12] Ian Goodfellow, Yoshua Bengio, and Aaron Courville. Deep Learning. MIT Press,
2016. http://www.deeplearningbook.org.
[14] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli
Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J.
Smith, Robert Kern, Matti Picus, Stephan Hoyer, Marten H. van Kerkwijk, Matthew
53
54 BIBLIOGRAFÍA
Brett, Allan Haldane, Jaime Fernández del Río, Mark Wiebe, Pearu Peterson, Pierre
Gérard-Marchant, Kevin Sheppard, Tyler Reddy, Warren Weckesser, Hameer Abba-
si, Christoph Gohlke, and Travis E. Oliphant. Array programming with NumPy.
Nature, 585(7825):357–362, September 2020.
[15] Dan Hendrycks, Steven Basart, Norman Mu, Saurav Kadavath, Frank Wang, Evan
Dorundo, Rahul Desai, Tyler Zhu, Samyak Parajuli, Mike Guo, Dawn Song, Jacob
Steinhardt, and Justin Gilmer. The many faces of robustness: A critical analysis of
out-of-distribution generalization. ICCV, 2021.
[17] Xiaogeng Liu, Haoyu Wang, Yechao Zhang, Fangzhou Wu, and Shengshan Hu. To-
wards efficient data-centric robust machine learning with noise-based augmenta-
tion, 2022.
[19] Adam Paszke, Sam Gross, Francisco Massa, Adam Lerer, James Bradbury, Gregory
Chanan, Trevor Killeen, Zeming Lin, Natalia Gimelshein, Luca Antiga, Alban Des-
maison, Andreas Kopf, Edward Yang, Zachary DeVito, Martin Raison, Alykhan Teja-
ni, Sasank Chilamkurthy, Benoit Steiner, Lu Fang, Junjie Bai, and Soumith Chintala.
Pytorch: An imperative style, high-performance deep learning library. In Advances
in Neural Information Processing Systems 32, pages 8024–8035. Curran Associates, Inc.,
2019.
[21] C Saravanan and M Surender. Enhancing efficiency of huffman coding using lem-
pel ziv coding for image compression. International Journal of Soft Computing and
Engineering, 2013.
[22] Jia Shijie, Wang Ping, Jia Peiyi, and Hu Siping. Research on data augmentation for
image classification based on convolution neural networks. In 2017 Chinese Automa-
tion Congress (CAC), pages 4165–4170, 2017.
[23] Connor Shorten and Taghi M. Khoshgoftaar. A survey on image data augmentation
for deep learning. Journal of Big Data, 6(1), 2019.
[25] Samajdar Tina and Md. Iqbal Quraishi. Analysis and evaluation of image quality
metrics. Springer, 340:369–378, Jan 2015. [Internet; descargado 11 Junio 2022].
[27] Stéfan van der Walt, Johannes L. Schönberger, Juan Nunez-Iglesias, François Bou-
logne, Joshua D. Warner, Neil Yager, Emmanuelle Gouillart, Tony Yu, and the scikit-
image contributors. scikit-image: image processing in Python. PeerJ, 2:e453, 6 2014.
[28] Qi Wang, Yue Ma, Kun Zhao, and Yingjie Tian. A comprehensive survey of loss
functions in machine learning. Annals of Data Science, 9(2):187–212, 2020.
[29] Zhou Wang, A.C. Bovik, H.R. Sheikh, and E.P. Simoncelli. Image quality assessment:
from error visibility to structural similarity. IEEE Transactions on Image Processing,
13(4):600–612, 2004.
BIBLIOGRAFÍA 55
[30] Yong Zhang and Donald A. Adjeroh. Prediction by partial approximate matching
for lossless image compression. IEEE Transactions on Image Processing, 17(6):924–935,
2008.
[31] Emir Öztürk and Altan Mesut. Performance evaluation of jpeg standards, webp
and png in terms of compression ratio and time for lossless encoding. In 2021 6th
International Conference on Computer Science and Engineering (UBMK), pages 15–20,
2021.
ANEXO
Grado de relación del trabajo con los Objetivos de Desarrollo Sostenible (ODS).
• Otros objetivos de desarrollo sostenible que están relacionados con este trabajo, aunque
en menor medida, serı́an: