El mundo pure que nos rodea es asombroso y enorme, sirve como hogar a incontables especies de animales y plantas a lo largo de todo el planeta. Si nos centramos en los animales, descubrimos a las aves, y dentro de estas, ciertos grupos más concretos como pueden ser las águilas, los buitres o los cuervos. La diversidad que existe en estos grupos es muy grande, haciendo que, a menudo, sea complicado diferenciar a easy vista la clase de ave que tenemos delante.
En lo que sigue, se mostrará la documentación del procedimiento llevado a cabo para desarrollar una página net que, haciendo uso de la cámara, tratará de identificar el ave que estamos viendo en ese momento.
Para llevar a cabo nuestro propósito, usaremos principalmente de programas de código abierto y algunas otras que ofrecen alternativas sin costo. Vamos a emplear Python 3.10, aprovechando bibliotecas tan populares y prácticas como NumPy, que nos facilitará el manejo de los arreglos numéricos, y Matplotlib, preferrred para las representaciones gráficas. Asimismo, recurriremos a bibliotecas para crear esas arquitecturas de redes neuronales, como TensorFlow 2.19.0, y Keras Tuner, una herramienta que nos ayudará encontrando los valores más convenientes (hiperparámetros) para nuestra pink, explorando entre diferentes posibilidades.
Para poner en marcha la página web, nos apoyaremos en Render para la parte del servidor (backend), donde manejaremos la lógica interna y las solicitudes de la app. En cuanto a la parte seen, el frontend, usaremos Vercel, que dará forma a la interfaz. Además, cada etapa del proyecto estará ligada a un repositorio en GitHub.
Para nuestra pink neuronal utilizaremos una herramienta muy poderosa, el Switch Studying o aprendizaje por transferencia. Esta técnica consiste en aprovechar modelos grandes previamente entrenados en tareas similares (como clasificación de imágenes) para reutilizar su conocimiento en nuestro problema. Para no entrenar un modelo desde cero, partimos de uno ya entrenado, MobileNetV2, y lo ajustamos para que aprenda a identificar las especies de aves de nuestros datos. Esto nos permite reducir el tiempo de entrenamiento y mejorar el rendimiento del modelo.
El primer paso es conocer nuestros datos y su estructura, en nuestro caso se encuentran en un servidor de la Universidad de Sonora, en un directorio llamado birds que tiene la siguiente estructura:
Cada uno de estos subdirectorios contiene 525 carpetas, con los nombre de una especie de ave distinta, compartiendo todas ellas clases de aves idénticas. Ahora, detallaremos el contenido de estos directorios y subdirectorios, además de aclarar el propósito que les daremos:
- check: Cada subdirectorio contiene 5 imágenes de aves correspondientes al nombre del subdirectorio. Usaremos este directorio para evaluar el desempeño ultimate del modelo una vez entrenado.
- prepare: Cada subdirectorio contiene alrededor de 150 imágenes de aves, según el nombre del subdirectorio. Este directorio lo utilizaremos para entrenar el modelo, permitiéndole aprender a reconocer las distintas especies.
- val: Cada subdirectorio contiene 5 imágenes de aves, según el nombre del subdirectorio. Finalmente, este directorio lo usaremos para validar el modelo durante el entrenamiento y ajustar los hiperparámetros, buscando evitar el sobreajuste.
Lo que haremos ahora es hacer un preprocesamiento de nuestros datos de entrenamiento (prepare), vamos a definir un generador de imágenes (ImageDataGenerator) para el conjunto de entrenamiento, encargado de preprocesar y aumentar las imágenes antes de pasarlas a la pink neuronal. Lo haremos con las siguientes especificaciones:
- rescale = 1./255. Normaliza los valores de píxeles entre 0 y 1.
- rotation_range = 40. Permite rotaciones aleatorias de hasta 40°.
- width_shift_range y height_shift_range = 0.3. Desplazamientos horizontales y verticales aleatorios del 30%.
- shear_range = 0.3. Aplica distorsión tipo shear del 30%.
- zoom_range = 0.3 . Realiza zoom aleatorio del 30%.
- horizontal_flip = True. Permite espejado horizontal.
- fill_mode = ‘nearest’. Rellena los bordes vacíos usando los valores de píxel más cercanos.
Para los datos de check y val, solamente aplicaremos un reescalamiento.
from tensorflow.keras.preprocessing.picture import ImageDataGeneratortrain_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.3,
height_shift_range=0.3,
shear_range=0.3,
zoom_range=0.3,
horizontal_flip=True,
fill_mode='nearest'
)
val_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
Como mencionamos antes usaremos la arquitectura de MobileNetV2, la cual tiene ciertas características, y una de ellas es el tamaño específico que deben tener las imágenes para poder ser procesadas correctamente. En este caso, ese tamaño es de (224, 224, 3), que corresponde al ancho, alto y número de canales de colour (RGB) de cada imagen. Por eso, definimos previamente una variable llamada IMG_SIZE con esas dimensiones. Ahora, gracias a flow_from_directory, usamos generadores que permiten al modelo leer las imágenes directamente desde las carpetas, redimensionarlas al tamaño correcto y organizarlas en bloques o “batches” para que puedan ser procesadas de manera eficiente. El generador de entrenamiento no solo hace este trabajo, sino que además aplica transformaciones (como rotaciones o espejados) y mezcla las imágenes para que el modelo aprenda de manera más robusta. En cambio, el generador de testeo y validación se limita a reescalar las imágenes, sin transformarlas ni mezclarlas, porque lo que buscamos allí es evaluar al modelo de mejor forma.
train_generator = train_datagen.flow_from_directory(
directory_train,
target_size=IMG_SIZE,
batch_size=BATCH_SIZE,
class_mode='categorical',
shuffle=True,
seed=42
)val_generator = val_datagen.flow_from_directory(
directory_val,
target_size=IMG_SIZE,
batch_size=BATCH_SIZE,
class_mode='categorical',
shuffle=False
)
test_generator = test_datagen.flow_from_directory(
directory_test,
target_size=IMG_SIZE,
batch_size=BATCH_SIZE,
class_mode='categorical',
shuffle=False
)
Una vez que tenemos listos los datos, pasamos a la construcción del modelo, que es el corazón de nuestro proyecto.
Esta es la parte más técnica del trabajo. Para resolver nuestro problema de identificación de aves, construimos un modelo de pink neuronal basado en MobileNetV2.
El modelo que diseñamos es versatile: gracias al uso de Keras Tuner, podemos explorar distintas combinaciones de hiperparámetros, como el tipo de función de activación, la cantidad de unidades en las capas densas, la tasa de dropout (que ayuda a evitar el sobreajuste) y la posibilidad de añadir una segunda capa densa.
import tensorflow as tffrom tensorflow import keras
from keras.fashions import Sequential, Mannequin
from keras.purposes.mobilenet_v2 import MobileNetV2
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, GlobalAveragePooling2D, BatchNormalization
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.regularizers import l2
def build_model(hp, fine_tune_at=None):
base_model = MobileNetV2(weights='imagenet', include_top=False, input_shape=IMG_SHAPE)
if fine_tune_at shouldn't be None:
base_model.trainable = True
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
else:
base_model.trainable = False
x = base_model.output
x = GlobalAveragePooling2D()(x)
activation_choice = hp.Selection('activation', ['relu', 'leaky_relu', 'elu', 'selu'])
dropout_rate = hp.Selection('dropout_rate', [0.0, 0.2, 0.3, 0.5])
learning_rate = hp.Selection('learning_rate', [1e-2, 1e-3, 1e-4])
l2_strength = hp.Float('l2_strength', 1e-5, 1e-2, sampling='log')
x = Dense(hp.Selection('dense_1_units', [128, 256]), kernel_regularizer=l2(l2_strength))(x)
x = BatchNormalization()(x)
x = _get_activation_layer(activation_choice)(x)
x = Dropout(dropout_rate)(x)
if hp.Boolean("add_second_dense"):
x = Dense(hp.Selection('dense_2_units', [64, 128]), kernel_regularizer=l2(l2_strength))(x)
x = BatchNormalization()(x)
x = _get_activation_layer(activation_choice)(x)
x = Dropout(dropout_rate)(x)
output = Dense(NUM_CLASSES, activation='softmax')(x)
mannequin = Mannequin(inputs=base_model.enter, outputs=output)
mannequin.base_model = base_model
mannequin.compile(optimizer=Adam(learning_rate=learning_rate),
loss='categorical_crossentropy',
metrics=['accuracy'])
return mannequin
Una vez construido nuestro modelo, queremos encontrar la combinación de configuraciones (o hiperparámetros) que le permita alcanzar su mejor rendimiento. Para eso utilizamos RandomSearch de Keras Tuner, que prueba de forma aleatoria distintas combinaciones de hiperparámetros. Definimos un máximo de 8 intentos, y como objetivo le pedimos que busque maximizar la precisión en validación.
Una vez finalizada esta búsqueda, seleccionamos el mejor modelo encontrado y lo entrenamos nuevamente durante 10 épocas para pulirlo aún más. Este proceso nos ayuda a construir un modelo ultimate mucho más robusto y adaptado a nuestros datos, sin necesidad de adivinar manualmente los mejores parámetros.
tuner = kt.RandomSearch(
build_model,
goal='val_accuracy',
max_trials=8,
overwrite=True,
listing='kt_logs',
project_name='bird_classification'
)tuner.search(train_generator, validation_data=val_generator, epochs=8)
best_model = tuner.get_best_models(1)[0]
historical past = best_model.match(
train_generator,
validation_data=val_generator,
epochs=10
)
Después de encontrar el mejor modelo con Keras Tuner, damos un paso adicional para exprimir aún más su rendimiento: aplicamos fine-tuning.
Esto es, desbloqueamos las últimas 30 capas del modelo base (MobileNetV2) para que puedan reajustarse específicamente a nuestro conjunto de aves. Las capas más profundas se mantienen congeladas, mientras que las capas superiores, que aprenden detalles más específicos, quedan libres para entrenar.
Recompilamos el modelo con una tasa de aprendizaje muy baja (de 1e-5). Luego, entrenamos nuevamente el modelo durante 20 épocas usando los mismos datos de entrenamiento y validación.
Finalmente, guardamos el modelo afinado en un archivo llamado modelo_birds_finetuned1.h5
.
base_model = best_model.base_model
base_model.trainable = Truefor layer in base_model.layers[:-30]:
layer.trainable = False
best_model.compile(optimizer=Adam(learning_rate=1e-5),
loss='categorical_crossentropy',
metrics=['accuracy'])
history_fine = best_model.match(
train_generator,
validation_data=val_generator,
epochs=20
)
best_model.save("modelo_birds_finetuned1.h5")
Podemos ver metricas del modelo, en nuestro caso veremos como evoluciona en cada epoca la perdida y la precisión sobre los conjuntos de entrenamiento y validacion.Podemos analizar las métricas del modelo; en nuestro caso, observaremos cómo evolucionan la pérdida (loss) y la precisión (accuracy) a lo largo de cada época, tanto en el conjunto de entrenamiento como en el de validación. Esto lo haremos en el apartado de resultados (Figura 5).
Sigamos adelante con el despliegue de nuestro proyecto, donde pondremos en marcha todo lo que construimos.
Una vez terminada la parte más técnica, podemos avanzar hacia el despliegue. Aquí crearemos una página net usando dos repositorios. En esta sección no entraremos en muchos detalles técnicos, ya que toda la información específica se puede consultar en el repositorio.
Backend
Como primer paso, creamos un repositorio en GitHub que nos servirá como backend. Este repositorio contendrá nuestro modelo entrenado (modelo_birds_finetuned1.h5
), un archivo necessities.txt
y un archivo important.py
. Las funciones de cada archivo son las siguientes:
modelo_birds_finetuned1.h5
: Archivo que guarda el modelo afinado, listo para hacer predicciones sobre nuevas imágenes de aves.necessities.txt
: Lista todas las bibliotecas necesarias para que nuestro backend funcione correctamente.important.py
: Archivo principal del backend, donde definimos cómo cargar el modelo, recibir las imágenes desde el frontend, procesarlas, obtener una predicción y enviar la respuesta de vuelta al usuario.
Una vez hecho esto, enlazamos nuestro repositorio desde Render, que será la plataforma encargada de desplegar el backend. Render se encarga de instalar automáticamente las dependencias listadas en necessities.txt
y de levantar el servidor definido en important.py
, permitiendo que nuestro modelo esté disponible a través de una url, la cual es elementary para el siguiente paso.
Frontend
Ahora, vamos a configurar un nuevo repositorio que nos servirá para el diseño de la página net, la cual será bastante sencilla y mayormente appropriate con móvil. En este repositorio incluiremos los logotipos e imágenes que queramos agregar a nuestra página, así como un archivo index.html
, que es el más importante, ya que será el encargado de definir la estructura y el contenido principal del sitio. En este archivo configuramos la interfaz de usuario, conectamos los elementos visuales con las funciones del backend y establecemos la forma en que se enviarán las imágenes para obtener las predicciones del modelo, es dentro de este archivo en donde llamamos por medio de ese url
otorgado por Render y solicitamos una predicción.
El diseño de la página net es sencillo y funcional. Incluye una breve descripción que explica el propósito de la aplicación, un recuadro donde se muestra en tiempo actual la imagen captada por la cámara del dispositivo, y dos botones: uno para cambiar entre la cámara trasera y frontal, y otro para capturar la imagen y enviarla al modelo para obtener la predicción del ave, mostrando el nombre de la especie.
Una vez construido el index.html
, el siguiente paso es muy related al anterior: enlazamos este nuevo repositorio desde Vercel, que se encargará de desplegar el frontend. Vercel nos proporcionará una nueva URL pública, la cual nos permitirá acceder y visualizar el resultado ultimate de la página net funcionando en línea.
A continuación, se muestra el resultado del diseño de la página web.
Después de completar el entrenamiento y fine-tuning del modelo, analizamos cómo evolucionaron sus métricas a lo largo de las épocas. Observamos principalmente dos métricas clave: la pérdida y la precisión, tanto en el conjunto de entrenamiento como en el de validación.
La siguiente figura muestra cómo evolucionaron la precisión (accuracy) y la pérdida (loss) durante el entrenamiento y la validación a lo largo de 20 épocas.
En el gráfico de la izquierda, observamos que la precisión en el conjunto de entrenamiento aumenta y precisión en validación llega a superar el 90% (0.9), lo que refleja un buen desempeño del modelo; mientras que en el gráfico de la derecha, vemos que la pérdida disminuye en ambos conjuntos, señal de que el modelo no se sobreajusta.
Creamos una matriz de confusión que incluye todas las especies, y en ella se observa una franja azul destacada en la diagonal principal, lo que confirma que el modelo obtuvo buenos resultados en el conjunto de prueba y cometió pocos errores de clasificación.
Una vez desplegada la página net, el sistema fue capaz de identificar correctamente aves en nuevas imágenes capturadas desde la cámara del dispositivo, mostrando en pantalla el nombre de la especie predicha. Las pruebas realizadas demostraron un buen funcionamiento.
Para ilustrar cómo funciona el modelo y la página net en acción, se grabó el siguiente video.
Finalmente, se muestran dos escenarios posibles que pueden ocurrir durante el uso del modelo, y que son completamente normales.
El primero es cuando se captura un ave muy parecida a otra especie o cuando la imagen resalta una característica engañosa, lo que puede llevar al modelo a realizar una predicción incorrecta. Esto sucede porque no es perfecto. Por ejemplo, en la siguiente imagen se muestra la especie Scarlet Macaw, pero el modelo la identifica erróneamente como Turkey Vulture.
El otro escenario que puede presentarse es cuando enfocamos a un ave cuya especie no forma parte de las 525 incluidas en nuestra base de datos. En estos casos, el modelo intentará igualmente hacer una predicción, eligiendo la clase con la mayor probabilidad, aunque no sea la correcta.
Por ejemplo, en la siguiente imagen se intentó predecir la especie Barnacle Goose, pero como esta no está entre nuestras clases, el modelo respondió con la especie Widespread Loon, que fue la que obtuvo la mayor puntuación de confianza.
En conclusión, este proyecto demuestra el poder de las redes neuronales, y en explicit de la arquitectura MobileNetV2, para identificar aves con alta precisión a partir de imágenes en tiempo actual. Aunque el modelo no es perfecto, logra buenos resultados en la mayoría de los casos. La integración net permite acercar esta tecnología al usuario de forma sencilla y práctica.