Búsqueda de imágenes con 🤗 conjuntos de datos

Búsqueda de imágenes con conjuntos de datos 🤗.

🤗 datasets es una biblioteca que facilita el acceso y compartición de conjuntos de datos. También facilita el procesamiento eficiente de datos, incluyendo el trabajo con datos que no caben en memoria.

Cuando se lanzó por primera vez datasets, estaba asociado principalmente con datos de texto. Sin embargo, recientemente, datasets ha agregado un mayor soporte para audio, así como imágenes. En particular, ahora existe un tipo de característica datasets para imágenes. Una publicación de blog anterior mostró cómo se puede utilizar datasets con 🤗 transformers para entrenar un modelo de clasificación de imágenes. En esta publicación de blog, veremos cómo podemos combinar datasets y algunas otras bibliotecas para crear una aplicación de búsqueda de imágenes.

Primero, instalaremos datasets. Dado que vamos a trabajar con imágenes, también instalaremos pillow. También necesitaremos sentence_transformers y faiss. Los presentaremos con más detalle a continuación. También instalaremos rich: solo lo usaremos brevemente aquí, pero es un paquete muy útil para tener, ¡realmente recomendaría explorarlo más a fondo!

!pip install datasets pillow rich faiss-gpu sentence_transformers 

Para comenzar, echemos un vistazo a la característica de imagen. Podemos usar la maravillosa biblioteca rich para explorar objetos de Python (funciones, clases, etc.)

from rich import inspect
import datasets

inspect(datasets.Image, help=True)

╭───────────────────────── <class 'datasets.features.image.Image'> ─────────────────────────╮
│ class Image(decode: bool = True, id: Union[str, NoneType] = None) -> None:                │
│                                                                                           │
│ Característica de imagen para leer datos de imágenes de un archivo de imagen.               │
│                                                                                           │
│ Entrada: La característica de imagen acepta como entrada:                                  │
│ - Una :obj:`str`: Ruta absoluta del archivo de imagen (es decir, se permite acceso         │
│ aleatorio).                                                                               │
│ - Un :obj:`dict` con las claves:                                                          │
│                                                                                           │
│     - path: Cadena con la ruta relativa del archivo de imagen al archivo de archivo.       │
│     - bytes: Bytes del archivo de imagen.                                                  │
│                                                                                           │
│   Esto es útil para archivos archivados con acceso secuencial.                             │
│                                                                                           │
│ - Un :obj:`np.ndarray`: matriz NumPy que representa una imagen.                           │
│ - Un :obj:`PIL.Image.Image`: objeto de imagen PIL.                                        │
│                                                                                           │
│ Argumentos:                                                                               │
│     decode (:obj:`bool`, valor predeterminado ``True``): Si se debe decodificar            │
│ los datos de la imagen. Si es `False`, devuelve el diccionario subyacente en el formato    │
│ {"path": image_path, "bytes": image_bytes}.                                               │
│                                                                                           │
│  decode = True                                                                            │
│   dtype = 'PIL.Image.Image'                                                               │
│      id = None                                                                            │
│ pa_type = StructType(struct<bytes: binary, path: string>)                                 │
╰───────────────────────────────────────────────────────────────────────────────────────────╯

Vemos que hay varias formas diferentes en las que podemos pasar nuestras imágenes. Volveremos a esto en un momento.

Una característica realmente agradable de la biblioteca datasets (más allá de la funcionalidad para procesar datos, asignación de memoria, etc.) es que obtienes algunas cosas “gratis”. Una de estas es la capacidad de agregar un índice faiss a un conjunto de datos. faiss es una “biblioteca para búsqueda eficiente de similitud y agrupación de vectores densos”.

La documentación de datasets muestra un ejemplo de uso de un índice faiss para recuperación de texto. En esta publicación veremos si podemos hacer lo mismo para imágenes.

El conjunto de datos: “Digitised Books – Imágenes identificadas como Embellecimientos. c. 1510 – c. 1900”

Este es un conjunto de datos de imágenes que se han extraído de una colección de libros digitalizados de la Biblioteca Británica. Estas imágenes provienen de libros de diferentes períodos de tiempo y de una amplia gama de dominios. Las imágenes se extrajeron utilizando información contenida en la salida OCR de cada libro. Como resultado, se sabe de qué libro provienen las imágenes, pero no necesariamente algo más sobre esa imagen, es decir, qué se muestra en la imagen.

Algunos intentos para superar esto han incluido subir las imágenes a flickr. Esto permite a las personas etiquetar las imágenes o colocarlas en diferentes categorías.

También ha habido proyectos para etiquetar el conjunto de datos utilizando aprendizaje automático. Este trabajo permite buscar por etiquetas, pero es posible que deseemos tener una capacidad de búsqueda más “rica”. Para este experimento en particular, trabajaremos con un subconjunto de las colecciones que contienen “adornos”. Este conjunto de datos es un poco más pequeño, por lo que será mejor para experimentar. Podemos obtener los datos completos del repositorio de datos de la British Library: https://doi.org/10.21250/db17. Dado que el conjunto de datos completo sigue siendo bastante grande, probablemente querrás comenzar con una muestra más pequeña.

Creando nuestro conjunto de datos

Nuestro conjunto de datos consiste en una carpeta que contiene subdirectorios en los que se encuentran las imágenes. Este es un formato bastante estándar para compartir conjuntos de datos de imágenes. Gracias a una solicitud de extracción recientemente fusionada, podemos cargar directamente este conjunto de datos utilizando el cargador datasets ImageFolder 🤯

from datasets import load_dataset
dataset = load_dataset("imagefolder", data_files="https://zenodo.org/record/6224034/files/embellishments_sample.zip?download=1")

Vamos a ver qué obtenemos de vuelta.

dataset

DatasetDict({
    train: Dataset({
        features: ['image', 'label'],
        num_rows: 10000
    })
})

Podemos obtener un DatasetDict y tenemos un conjunto de datos con características de imagen y etiqueta. Dado que aquí no tenemos ninguna división de entrenamiento / validación, vamos a tomar la parte de entrenamiento de nuestro conjunto de datos. También echemos un vistazo a un ejemplo de nuestro conjunto de datos para ver cómo se ve esto.

dataset = dataset["train"]
dataset[0]

{'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=358x461 at 0x7F9488DBB090>,
 'label': 208}

Empecemos con la columna de etiquetas. Contiene la carpeta principal de nuestras imágenes. En este caso, la columna de etiquetas representa el año de publicación de los libros de los cuales se tomaron las imágenes. Podemos ver las asignaciones para esto usando dataset.features:

dataset.features['label']

En este conjunto de datos en particular, los nombres de archivo de las imágenes también contienen metadatos sobre el libro del cual se tomó la imagen. Hay algunas formas en las que podemos obtener esta información.

Cuando vemos un ejemplo de nuestro conjunto de datos, la característica image era un PIL.JpegImagePlugin.JpegImageFile. Dado que las PIL.Images tienen un atributo de nombre de archivo, una forma en la que podemos obtener nuestros nombres de archivo es accediendo a esto.

dataset[0]['image'].filename

/root/.cache/huggingface/datasets/downloads/extracted/f324a87ed7bf3a6b83b8a353096fbd9500d6e7956e55c3d96d2b23cc03146582/embellishments_sample/1920/000499442_0_000579_1_[The Ring and the Book  etc ]_1920.jpg

Dado que es posible que deseemos acceder fácilmente a esta información más adelante, creemos una nueva columna para extraer el nombre del archivo. Para esto, usaremos el método map.

dataset = dataset.map(lambda example: {"fname": example['image'].filename.split("/")[-1]})

Podemos ver un ejemplo para ver cómo se ve esto ahora.

dataset[0]

{'fname': '000499442_0_000579_1_[The Ring and the Book  etc ]_1920.jpg',
 'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=358x461 at 0x7F94862A9650>,
 'label': 208}

Ya tenemos nuestros metadatos. ¡Veamos algunas imágenes ya! Si accedemos a un ejemplo e indexamos en la columna image, veremos nuestra imagen 😃

dataset[10]['image']

Nota: en una versión anterior de esta publicación del blog, los pasos para descargar y cargar las imágenes eran mucho más complicados. El nuevo cargador de ImageFolder hace este proceso mucho más fácil 😀 En particular, no necesitamos preocuparnos por cómo cargar nuestras imágenes, ya que los conjuntos de datos se encargaron de esto por nosotros.

¡Envía todas las cosas al hub!

Una de las cosas súper geniales del ecosistema 🤗 es el Hugging Face Hub. Podemos usar el Hub para acceder a modelos y conjuntos de datos. A menudo se utiliza para compartir trabajo con otros, pero también puede ser una herramienta útil para el trabajo en progreso. datasets recientemente agregó un método push_to_hub que te permite enviar un conjunto de datos al Hub con la menor molestia posible. Esto puede ser realmente útil al permitirte pasar alrededor de un conjunto de datos con todas las transformaciones, etc. ya realizadas.

Por ahora, enviaremos el conjunto de datos al Hub y lo mantendremos privado inicialmente.

Dependiendo de dónde estés ejecutando el código, es posible que necesites autenticarte. Puedes hacer esto usando el comando huggingface-cli login o, si estás ejecutando en un notebook, usando notebook_login

from huggingface_hub import notebook_login

notebook_login()

dataset.push_to_hub('davanstrien/embellishments-sample', private=True)

Nota: en una versión anterior de esta publicación de blog, teníamos que realizar algunos pasos adicionales para asegurarnos de que las imágenes se incrustaran al usar push_to_hub. Gracias a esta solicitud de extracción, ya no necesitamos preocuparnos por estos pasos adicionales. Solo necesitamos asegurarnos de que embed_external_files=True (que es el comportamiento predeterminado).

Cambiando de máquinas

En este punto, hemos creado un conjunto de datos y lo hemos movido al Hub. Esto significa que es posible retomar el trabajo/conjunto de datos en otro lugar.

En este ejemplo en particular, tener acceso a una GPU es importante. Utilizando el Hub como una forma de pasar alrededor de nuestros datos, podríamos comenzar en una laptop y continuar el trabajo en Google Colab.

Si nos mudamos a una nueva máquina, es posible que necesitemos iniciar sesión nuevamente. Una vez que hayamos hecho esto, podemos cargar nuestro conjunto de datos

from datasets import load_dataset

dataset = load_dataset("davanstrien/embellishments-sample", use_auth_token=True)

Creando embeddings 🕸

Ahora tenemos un conjunto de datos con un montón de imágenes. Para comenzar a crear nuestra aplicación de búsqueda de imágenes, necesitamos incrustar estas imágenes. Hay varias formas de intentar hacer esto, pero una posible forma es utilizar los modelos CLIP a través de la biblioteca sentence_transformers. El modelo CLIP de OpenAI aprende una representación conjunta tanto para imágenes como para texto, lo cual es muy útil para lo que queremos hacer, ya que queremos ingresar texto y obtener una imagen de vuelta.

Podemos descargar el modelo utilizando la clase SentenceTransformer.

from sentence_transformers import SentenceTransformer

model = SentenceTransformer('clip-ViT-B-32')

Este modelo tomará como entrada una imagen o algún texto y devolverá una incrustación. Podemos usar el método map de datasets para codificar todas nuestras imágenes utilizando este modelo. Cuando llamamos a map, devolvemos un diccionario con la clave embeddings que contiene las incrustaciones devueltas por el modelo. También pasamos device='cuda' cuando llamamos al modelo; esto asegura que estemos realizando la codificación en la GPU.

ds_with_embeddings = dataset.map(
    lambda example: {'embeddings':model.encode(example['image'], device='cuda')}, batched=True, batch_size=32)

Podemos ‘guardar’ nuestro trabajo enviándolo de vuelta al Hub usando push_to_hub.

ds_with_embeddings.push_to_hub('davanstrien/embellishments-sample', private=True)

Si nos mudáramos a una máquina diferente, podríamos obtener nuestro trabajo nuevamente cargándolo desde el Hub 😃

from datasets import load_dataset

ds_with_embeddings = load_dataset("davanstrien/embellishments-sample", use_auth_token=True)

Ahora tenemos una nueva columna que contiene las incrustaciones de nuestras imágenes. Podríamos buscar manualmente a través de ellas y compararlas con alguna incrustación de entrada, pero datasets tiene un método add_faiss_index. Esto utiliza la biblioteca faiss para crear un índice eficiente para buscar incrustaciones. Para obtener más información sobre esta biblioteca, puedes ver este video de YouTube

ds_with_embeddings['train'].add_faiss_index(column='embeddings')

Dataset({
        features: ['fname', 'year', 'path', 'image', 'embeddings'],
        num_rows: 10000
    })

Nota que estos ejemplos fueron generados a partir de la versión completa del conjunto de datos, por lo que es posible que obtenga resultados ligeramente diferentes.

Ahora tenemos todo lo que necesitamos para crear una búsqueda de imágenes sencilla. Podemos usar el mismo modelo que utilizamos para codificar nuestras imágenes para codificar un poco de texto de entrada. Esto actuará como la indicación que intentamos encontrar ejemplos cercanos. Comencemos con ‘una máquina de vapor’.

prompt = model.encode("Una máquina de vapor")

Podemos usar otro método de la biblioteca datasets get_nearest_examples para obtener imágenes que tengan una incrustación cercana a nuestra incrustación de indicación de entrada. Podemos pasar el número de resultados que queremos obtener.

puntajes, ejemplos_recuperados = ds_with_embeddings['train'].get_nearest_examples('embeddings', prompt, k=9)

Podemos acceder al primer ejemplo que se recupera:

ejemplos_recuperados['image'][0]

Esto no es exactamente una máquina de vapor, pero tampoco es un resultado completamente extraño. Podemos trazar los otros resultados para ver qué se devolvió.

import matplotlib.pyplot as plt

plt.figure(figsize=(20, 20))
columnas = 3
for i in range(9):
    imagen = ejemplos_recuperados['image'][i]
    plt.subplot(9 / columnas + 1, columnas, i + 1)
    plt.imshow(imagen)

Algunos de estos resultados se parecen bastante a nuestra indicación de entrada. Podemos envolver esto en una función para poder experimentar más fácilmente con diferentes indicaciones.

def obtener_imagen_desde_texto(indicacion_texto, numero_a_recuperar=9):
    indicacion = model.encode(indicacion_texto)
    puntajes, ejemplos_recuperados = ds_with_embeddings['train'].get_nearest_examples('embeddings', indicacion, k=numero_a_recuperar)
    plt.figure(figsize=(20, 20))
    columnas = 3
    for i in range(9):
        imagen = ejemplos_recuperados['image'][i]
        plt.title(indicacion_texto)
        plt.subplot(9 / columnas + 1, columnas, i + 1)
        plt.imshow(imagen)

obtener_imagen_desde_texto("Una ilustración del sol detrás de una montaña")

Probando varias indicaciones ✨

Ahora que tenemos una función para obtener algunos resultados, podemos probar diferentes indicaciones:

  • Para algunos de estos, elegiré indicaciones que sean una categoría amplia, es decir, ‘un instrumento musical’ o ‘un animal’, otros son específicos, es decir, ‘una guitarra’.

  • Por curiosidad, también probé un operador booleano: “Una ilustración de un gato o un perro”.

  • Finalmente, probé algo un poco más abstracto: “un abismo vacío”.

indicaciones = ["Un instrumento musical", "Una guitarra", "Un animal", "Una ilustración de un gato o un perro", "un abismo vacío"]

for indicacion in indicaciones:
    obtener_imagen_desde_texto(indicacion)

Podemos ver que estos resultados no siempre son correctos, pero generalmente son razonables. Parece que esto podría ser útil para buscar el contenido semántico de una imagen en este conjunto de datos. Sin embargo, es posible que evitemos compartir esto tal como está…

Creación de un espacio de Hugging Face? 🤷🏼

Un siguiente paso obvio para este tipo de proyecto es crear una demostración de un espacio de Hugging Face. Esto es lo que he hecho para otros modelos.

Fue un proceso bastante sencillo configurar una aplicación Gradio desde el punto en el que llegamos aquí. Aquí hay una captura de pantalla de esta aplicación:

Sin embargo, estoy un poco cauteloso acerca de hacer esto público de inmediato. Al observar la tarjeta del modelo para el modelo CLIP, podemos ver los usos principales previstos:

Usos principales previstos

Principalmente imaginamos que el modelo será utilizado por investigadores para comprender mejor la robustez, generalización y otras capacidades, sesgos y limitaciones de los modelos de visión por computadora. fuente

Esto se acerca bastante a lo que nos interesa aquí. Especialmente podríamos estar interesados en cómo el modelo se maneja con los tipos de imágenes en nuestro conjunto de datos (ilustraciones de libros principalmente del siglo XIX). Las imágenes en nuestro conjunto de datos son (probablemente) bastante diferentes de los datos de entrenamiento. El hecho de que algunas de las imágenes también contengan texto podría ayudar a CLIP, ya que muestra cierta capacidad de OCR.

Sin embargo, al observar los casos de uso fuera de alcance en la tarjeta del modelo:

Casos de uso fuera de alcance

Cualquier caso de uso implementado del modelo, ya sea comercial o no, está actualmente fuera de alcance. Los casos de uso no implementados, como la búsqueda de imágenes en un entorno restringido, tampoco se recomiendan a menos que haya pruebas exhaustivas en el dominio del modelo con una taxonomía de clase específica y fija. Esto se debe a que nuestra evaluación de seguridad demostró una gran necesidad de pruebas específicas de tareas, especialmente dada la variabilidad del rendimiento de CLIP con diferentes taxonomías de clase. Esto hace que la implementación no probada y no restringida del modelo en cualquier caso de uso sea potencialmente perjudicial en la actualidad. fuente

sugiere que la “implementación” no es una buena idea. Si bien los resultados que obtuve son interesantes, todavía no he jugado lo suficiente con el modelo (y no he hecho nada más sistemático para evaluar su rendimiento y sesgos) como para tener confianza en “implementarlo”. Otra consideración adicional es el conjunto de datos objetivo en sí mismo. Las imágenes se extraen de libros que cubren una variedad de temas y períodos de tiempo. Hay muchos libros que representan actitudes coloniales y, como resultado, algunas de las imágenes incluidas pueden representar a ciertos grupos de personas de manera negativa. Esto podría ser potencialmente una mala combinación con una herramienta que permite que cualquier entrada de texto arbitraria se codifique como una indicación.

Puede haber formas de solucionar este problema, pero esto requerirá un poco más de reflexión.

Conclusión

Aunque no tenemos una demostración interesante para mostrar, hemos visto cómo podemos usar datasets para:

  • cargar imágenes en el nuevo tipo de característica Image
  • ‘guardar’ nuestro trabajo usando push_to_hub y utilizar esto para mover datos entre máquinas/sesiones
  • crear un índice faiss para imágenes que podemos usar para recuperar imágenes a partir de una entrada de texto (o imagen).

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Soñar primero, aprender después DECKARD es un enfoque de IA que utiliza LLMs para entrenar agentes de aprendizaje por refuerzo (RL)

El aprendizaje por refuerzo (RL) es un enfoque popular para entrenar agentes autónomos que pueden aprender a realizar...

Inteligencia Artificial

Combatir la suplantación de identidad por la IA

Encontrar formas de determinar si un mensaje de voz es real o generado por una inteligencia artificial.

Inteligencia Artificial

Este documento de IA presenta técnicas avanzadas para explicaciones detalladas de texto y visual en modelos de alineación de imágenes y texto.

Los modelos de alineación de texto e imagen tienen como objetivo establecer una conexión significativa entre el conte...

Inteligencia Artificial

El futuro de la guerra totalmente autónoma impulsado por IA está aquí

Barcos sin tripulación. Enjambres de drones autónomos. Cómo una fuerza de tarea de la Armada de los Estados Unidos es...

Inteligencia Artificial

Investigadores de DeepMind redefinen el Aprendizaje Reforzado Continuo con una precisa definición matemática

Los avances recientes en el aprendizaje profundo por refuerzo (RL) han demostrado un rendimiento sobrehumano por part...

Inteligencia Artificial

Conoce a Prismer Un modelo de visión-lenguaje de código abierto con un conjunto de expertos.

Varios modelos recientes de visión y lenguaje han demostrado notables habilidades de generación multimodal. Pero típi...