Eleva tus habilidades en deep learning aplicando transfer learning a modelos pre-entrenados. Aprende a utilizar todo el poder de la biblioteca de modelos de machine learning y datasets open-source de Hugging Face de la mano de sus expertos.
- Comparte tus modelos en el Hub de Hugging Face.
- Afina modelos de visión computarizada y procesamiento de lenguaje natural.
- Aplica transfer learning de forma sencilla y rápida.
- Utiliza el Hub de Hugging Face y su biblioteca de modelos.
Antes de continuar te invito a que revises los cursos anteriores:
- 1: Curso profesional de Redes Neuronales con TensorFlow
- 2: Curso de Redes Neuronales Convolucionales con Python y keras
- 3: Curso profesional de Redes Neuronales con TensorFlow
Este Curso es el Número 4 de una ruta de Deep Learning, quizá algunos conceptos no vuelvan a ser definidos en este repositorio, por eso es indispensable que antes de empezar a leer esta guía hayas comprendido los temas vistos anteriormente.
Sin más por agregar disfruta de este curso
- 1 Introducción al Hub de Hugging Face
- 2 Primeros pasos con transfer learning y transformers
- 3 Computer Vision
- 4 Natural Language Processing
- 5 Comparte en el HUB
Una breve introducción a Hugging Face
El Hub de Hugging Face es una plataforma en línea que permite a los desarrolladores de inteligencia artificial compartir, descubrir y usar modelos pre-entrenados de aprendizaje profundo y otros recursos de procesamiento de lenguaje natural (NLP). Hugging Face es una empresa de tecnología de inteligencia artificial que se especializa en herramientas y modelos de NLP, y su Hub es una parte importante de su plataforma.
En el Hub de Hugging Face, los usuarios pueden encontrar una amplia variedad de modelos de NLP pre-entrenados en varios idiomas, incluyendo modelos para tareas como la traducción automática, el reconocimiento de entidades nombradas, la generación de lenguaje natural y mucho más. Los modelos están disponibles en diferentes tamaños y configuraciones, lo que permite a los usuarios encontrar el modelo adecuado para su caso de uso específico.
Además de los modelos pre-entrenados, el Hub también contiene recursos de datos, códigos y herramientas para ayudar a los desarrolladores a construir y mejorar sus propios modelos de NLP. La plataforma es de código abierto, lo que significa que los usuarios pueden contribuir y mejorar los recursos existentes para el beneficio de la comunidad en general.
¿Qué aprenderemos en este curso?
- Qué es y cómo hacer transfer learning?
- Qué es el Hub de Hugging Face y cómo usarlo
- Utilizar modelos modernos para nuestras propias aplicaciones.
- Afinar modelos de lenguaje y visión
- Utilizar datasets open source compartidos por la comunidad en el Hub
Bases de Transfer Learning
Transfer learning (aprendizaje transferido, en español) es una técnica de aprendizaje automático en la que se aprovecha el conocimiento adquirido de un modelo entrenado en una tarea para mejorar el desempeño de otro modelo en una tarea relacionada.
En lugar de entrenar un modelo desde cero para una tarea específica, el aprendizaje transferido utiliza un modelo previamente entrenado en una tarea similar como punto de partida y ajusta sus parámetros para la tarea en cuestión. Esto se logra mediante la reutilización de algunas o todas las capas de la red neuronal del modelo previo, que contienen conocimientos generales sobre el lenguaje o las imágenes.
¿Por qué deberíamos utilizar Transfer Learning?
El beneficio de esta técnica es que puede reducir significativamente la cantidad de datos y el tiempo necesarios para entrenar un modelo para una nueva tarea, especialmente si la tarea en cuestión tiene una cantidad limitada de datos de entrenamiento disponibles. Además, el modelo pre-entrenado puede contener características generales útiles para tareas relacionadas, lo que puede mejorar la calidad de la predicción.
El aprendizaje transferido se ha demostrado que es efectivo en una amplia variedad de tareas de aprendizaje automático, incluyendo la clasificación de imágenes, el procesamiento del lenguaje natural, el reconocimiento de voz, entre otros.
En la actualidad es MUY complejo e incluso inocente pensar que alguna empresa será capaz de "resolver la IA" por sí misma.
Esto debido al alto nivel de complejidad de la tarea. Este tipo de problemas requieren el esfuerzo de una gran cantidad de
personas trabajando por un bien común. Se requiere de una comunidad
. Tener a más personas contribuyendo en los proyectos de
IA es la forma más efectiva y eficiente de generar mejores y más poderosos modelos.
Actualmente, Hugging Face
es una comunidad de IA en donde podremos encontrar:
- más 50 mil modelos
- más de 5 mil datasets
- más de 5 mil Spaces (demos)
Los modelos son libres, y podemos usarlos como base para crear nuestras nuevas propuestas de modelos y solucionar problemas cada vez más específicos.
Empecemos por conocer la página de Hugging Face: https://huggingface.co/
De forma muy sencilla, podemos decir que dentro de Hugging Face, cada modelo es en sí mismo un repositorio de Git. Hugging Face nos permite filtrar por una amplia variedad de parámetros, desde el tipo de problema: Multimodal, NLP, Audio, Computer Vision. Hasta por el tipo de biblioteca que manejamos: Pytorch, TensorFlow, Keras, Scikit-learn etc. Por idioma, por licencia entre otros.
Nuestros filtros no arrojaran que modelos cumplen con nuestros requisitos:
Por ejemplo conozcamos a: nlptown/bert-base-multilingual-uncased-sentiment
Podemos observar como es una página bastante similar a GitHub. Incluso en su pestaña de Files and versions
podemos encontrar
una estructura muy similar a Github.
Varios modelos de Hugging Face me van a permitir usar su Hosted inference API
Una herramienta sumamente práctica que me va a dejar probar el funcionamiento del modelo de una forma muy simple.
Por ejemplo: busquemos xlm-roberta-base
De forma sumamente rápida podemos ver el funcionamiento de este modelo. Inlcuso en la ventaja de Use in Transformers
nos dan un ejemplo de implementación simple en código:
En esta clase aprenderemos ¿Qué son los tasks? Y ¿Cómo elegir el mejor modelo para nuestra aplicación?
Una task es una aplicación específica para la que creas un modelo de Machine Learning. Por ejemplo, pensemos en las siguientes
áreas de aplicación de Deep Learning y mencionemos algunas de sus tasks
:
- Computer Vision:
- Image Classification
- Image Segmentation
- Image-to-image
- Unconditional Image Generation
- Object Detection
- Natural Language Processing
- Translation
- Token Classification
- Sentence Similarity
- Question Answering
- Summarization
- Zero-Shot Classification
- Text Classification
- Text2Text Generation
- Audio
- Audio-Classification
- Text-to-Speech
- Audio-to-Audio
- Multimodal
- Feature Extraction
- Image-to-Text
- Text-to-Image
- Tabular
- Tabular Classification
- Tabular Regression
- Reinforcement Learning
- Reinforcement Learning
Todos estos y más tasks
están disponibles en Hugging Face
: https://huggingface.co/tasks
Cada task
nos brinda más información al respecto del mismo, modelos disponibles, bibliotecas compatibles y en algunos
casos incluso códigos de ejemplo en formato NoteBook e incluso Datasets. Por ejemplo veamos el task de Text Classification
Puedes encontrar el notebook completo de esta clase: Aquí
En esta clase vamos a ver una forma sumamente simple de utilizar pipeline
para utilizar modelos pre-entrenados de Hugging Face.
El código completo de todas las pruebas se encuentra en el notebook descrito anteriormente. Sin embargo, en esta sección
solamente vamos a presentar un solo ejemplo, puesto que en general todos son básicamente lo mismo.
Primero debemos empezar instalando la biblioteca de transformers
de Hugging Face
pip install transformers
Respuesta esperada:
Successfully installed filelock-3.10.7 huggingface-hub-0.13.3 pyyaml-6.0 regex-2023.3.23 tokenizers-0.13.2 transformers-4.27.3
Imaginemos que queremos clasificar una imagen y qué un modelo nos diga que entidad es capaz de reconocer en dicha imagen. Por ejemplo la siguiente:
Nosotros ya sabemos que se trata de un coche
, pero veamos que tan fácil es para un modelo de Hugging Face
determinar esto:
from transformers import pipeline
if __name__ == '__main__':
obj_classification = pipeline(task="image-classification")
ans = obj_classification("coche.png")
print(ans)
Nota:
Para este ejemplo de código hemos puesto
lo mínimo
indispensable para correr un modelo a través de pipeline, lo cual es especificar quetask
estamos buscando resolver. En este ejemplo ha sidoimage-classification
, sin embargo, no hemos puesto ningún otro parámetro, como por ejemplo:model
. Esto obliga aHugging Face
a inferir cuál sería el mejor modelo que se adapte a nuestra necesidad, automáticamente propone un modelo y lo descarga por nosotrosNo model was supplied, defaulted to google/vit-base-patch16-224 and revision 5dca96d (https://huggingface.co/google/vit-base-patch16-224). Using a pipeline without specifying a model name and revision in production is not recommended. Downloading (…)lve/main/config.json: 100%|██████████| 69.7k/69.7k [00:00<00:00, 369kB/s] Downloading tf_model.h5: 100%|██████████| 347M/347M [00:36<00:00, 9.44MB/s]
Respuesta esperada:
[{'score': 0.6160857677459717, 'label': 'minivan'}, {'score': 0.2290043979883194, 'label': 'beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon'}, {'score': 0.03376132249832153, 'label': 'car wheel'}, {'score': 0.027296774089336395, 'label': 'jeep, landrover'}, {'score': 0.020214203745126724, 'label': 'grille, radiator grille'}]
Excelente, el modelo ha clasificado exitosamente a nuestra imagen no solo como un coche, sino que especifica mente un minivan
Con esto hemos logrado resolver un problema de clasificación de imágenes con simplemente 3 líneas de código. Ha sido sumamente sencillo
usar pipeline
y dejar que Hugging Face
se encargase de todo lo demás por nosotros.
Sin embargo, cabe destacar que la clase pipeline
tiene varios parámetros configurables, entre ellos podemos encontrar los siguientes:
task (str)
— The task defining which pipeline will be returned. Currently accepted tasks are:
- "audio-classification": will return a AudioClassificationPipeline.
- "automatic-speech-recognition": will return a AutomaticSpeechRecognitionPipeline.
- "conversational": will return a ConversationalPipeline.
- "depth-estimation": will return a DepthEstimationPipeline.
- "document-question-answering": will return a DocumentQuestionAnsweringPipeline.
- "feature-extraction": will return a FeatureExtractionPipeline.
- "fill-mask": will return a FillMaskPipeline:. -"image-classification": will return a ImageClassificationPipeline.
- "image-segmentation": will return a ImageSegmentationPipeline.
- "image-to-text": will return a ImageToTextPipeline.
- "object-detection": will return a ObjectDetectionPipeline.
- "question-answering": will return a QuestionAnsweringPipeline.
- "summarization": will return a SummarizationPipeline.
- "table-question-answering": will return a TableQuestionAnsweringPipeline.
- "text2text-generation": will return a Text2TextGenerationPipeline.
- "text-classification" (alias "sentiment-analysis" available): will return a TextClassificationPipeline.
- "text-generation": will return a TextGenerationPipeline:.
- "token-classification" (alias "ner" available): will return a TokenClassificationPipeline.
- "translation": will return a TranslationPipeline.
- "translation_xx_to_yy": will return a TranslationPipeline.
- "video-classification": will return a VideoClassificationPipeline.
- "visual-question-answering": will return a VisualQuestionAnsweringPipeline.
- "zero-shot-classification": will return a ZeroShotClassificationPipeline.
- "zero-shot-image-classification": will return a ZeroShotImageClassificationPipeline.
- "zero-shot-audio-classification": will return a ZeroShotAudioClassificationPipeline.
- "zero-shot-object-detection": will return a ZeroShotObjectDetectionPipeline.
model (str or PreTrainedModel or TFPreTrainedModel, optional)
— The model that will be used by the pipeline to make predictions. This can be a model identifier or an actual instance of a pretrained model inheriting from PreTrainedModel (for PyTorch) or TFPreTrainedModel (for TensorFlow). If not provided, the default for the task will be loaded.
config (str or PretrainedConfig, optional)
— The configuration that will be used by the pipeline to instantiate the model. This can be a model identifier or an actual pretrained model configuration inheriting from PretrainedConfig. If not provided, the default configuration file for the requested model will be used. That means that if model is given, its default configuration will be used. However, if model is not supplied, this task’s default model’s config is used instead.
Puedes leer la documentación completa en: The pipeline abstraction
Hugging Face tiene una gran variedad de datasets
públicos y disponibles, puedes acceder a ellos en: https://huggingface.co/datasets
De igual forma que los modelos, los datasets cuentan con la barra de búsqueda de la izquierda, en donde podremos filtrar
por task
, por idioma, licencia entre otros.
Cuando entras a alguno de ellos, por ejemplo: imdb
Puedes observar: un preview del dataset, una descripción de qué es y para qué sirve, e incluso modelos que lo utilicen,
ya sea que hayan sido entrenados con dicha base o fine-tuned
.
El mundo de los transformers
en Deep Learning nace en 2017 de la mano del siguiente artículo: Attention is all you need
A continuación un breve resumen del paper:
"Attention is All You Need" es un artículo presentado en la conferencia de 2017 sobre Aprendizaje Profundo y Aprendizaje Representacional de la Asociación para el Avance de la Inteligencia Artificial (AAAI). El artículo propone una nueva arquitectura de red neuronal llamada Transformer, que utiliza exclusivamente mecanismos de atención para procesar secuencias de texto.
La arquitectura Transformer fue diseñada para superar las limitaciones de las arquitecturas de redes neuronales recurrentes (RNN) y de las arquitecturas basadas en convoluciones (CNN), que habían sido las más utilizadas para procesar secuencias de texto hasta ese momento. La principal ventaja de la arquitectura Transformer es que elimina la necesidad de utilizar una red neuronal recurrente o una red neuronal convolucional para procesar las secuencias de entrada, lo que permite una mayor paralelización del procesamiento y un entrenamiento más rápido de la red.
La arquitectura Transformer utiliza capas de auto-atención, en las que cada elemento de la secuencia de entrada se relaciona con todos los demás elementos de la secuencia para producir una representación contextualizada de cada elemento. Esto se logra mediante la combinación de tres subcapas: una capa de auto-atención multi-cabeza, una capa de normalización de capa y una capa de red neuronal completamente conectada. El uso de múltiples cabezas de atención permite a la red aprender diferentes relaciones entre los elementos de la secuencia, lo que mejora su capacidad para procesar secuencias de texto.
El artículo demuestra la eficacia de la arquitectura Transformer en varias tareas de procesamiento de lenguaje natural, incluyendo la traducción automática y el modelado del lenguaje. La arquitectura Transformer ha demostrado ser muy exitosa y ha sido utilizada en varias aplicaciones de procesamiento de lenguaje natural desde entonces.
A continuación, explicaremos con más detalle cómo funcionan los transformers:
-
Representación de la entrada:
La primera capa de la red transforma la entrada (por ejemplo, una secuencia de palabras) en una representación vectorial de alta dimensión. Cada elemento de la secuencia se representa mediante un vector de palabras (word embedding), que es una representación densa de baja dimensión de la palabra. -
Módulo de atención:
La siguiente capa de la red utiliza un mecanismo de atención para calcular una puntuación de atención para cada elemento de la secuencia. Esto permite que la red se centre en partes específicas de la entrada mientras procesa la secuencia completa. Los valores de atención se calculan como un producto escalar entre vectores de consulta (query) y vectores de clave (key) asociados a cada elemento de la secuencia. El resultado se normaliza y se utiliza para ponderar los vectores de valor (value) asociados a cada elemento de la secuencia. -
Capas de transformación:
Después del módulo de atención, la red utiliza varias capas de transformación para procesar la entrada. Cada capa de transformación se compone de varias subcapas, incluyendo una capa de normalización y dos capas completamente conectadas (feedforward). Estas capas ayudan a la red a aprender representaciones más complejas de la entrada y permiten que la red se adapte a una variedad de tareas diferentes. -
Capa de salida:
Finalmente, la última capa de la red transforma la representación de salida en una salida específica para la tarea. Por ejemplo, si la tarea es clasificar el sentimiento de una reseña de película, la capa de salida podría ser una capa de clasificación que produce una etiqueta de sentimiento (por ejemplo, positivo o negativo) a partir de la representación de la entrada.
Para una información más simple y amena de entender: Las REDES NEURONALES ahora prestan ATENCIÓN
Ampliamente Recomendable leer Cómo funcionan los transformers
Te recomiendo ampliamente leer el capítulo 5 Introducción al aprendizaje por transferencia de mi curso anterior: Curso profesional de Redes Neuronales con TensorFlow
en dónde encontrarás información detallada del proceso de transfer learning
, adicionalmente encontrarás códigos ejemplo
de la implementación base en TensorFlow
aquí el proceso se verá de forma más superficial, sin embargo, te comparto un breve
resumen y recordatorio de como funciona transfer learning
y fine tuning
Transfer learning y fine-tuning son técnicas populares en el campo del aprendizaje profundo que se utilizan para mejorar la capacidad de generalización de las redes neuronales y reducir el tiempo y el costo de entrenamiento.
-
Transfer Learning:
Esta técnica se basa en el uso de un modelo pre-entrenado en una tarea relacionada para inicializar la red neuronal y luego ajustarla a una tarea específica. El modelo pre-entrenado se entrena en una gran cantidad de datos y se ha demostrado que es eficaz en la extracción de características generales de la entrada. En lugar de entrenar la red desde cero en una tarea específica, se utiliza el modelo pre-entrenado como punto de partida y se ajusta para la tarea específica utilizando datos adicionales. La idea es que la red neuronal pre-entrenada ya haya aprendido algunas características generales útiles que se pueden reutilizar para la tarea específica. Esto ahorra tiempo y costo en el entrenamiento de la red neuronal. -
Fine-tuning:
Es una técnica de ajuste fino que se utiliza después de la transferencia de aprendizaje. La red neuronal pre-entrenada se ajusta (fine-tune) en la tarea específica mediante el entrenamiento en datos adicionales. En lugar de ajustar la red neuronal completa, se ajustan sólo las últimas capas de la red neuronal para que se ajusten mejor a la tarea específica. Esto se hace porque las últimas capas de la red neuronal son las más especializadas en la tarea específica, mientras que las capas anteriores pueden ser más generales.
Para este capítulo estaremos trabajando con una tarea de clasificación de imágenes. Nuestro problema será resolver el problema del dataset beans el cual contiene imágenes de hojas de frijol y queremos clasificar entre 3 tipos de clases:
{
"angular_leaf_spot": 0,
"bean_rust": 1,
"healthy": 2,
}
El dataset contiene 3 particiones: Train 1034 imágenes, Validation 133 imágenes, Test 128 imágenes.
Para ello usaremos transfer learning
, usaremos como modelo base google / vit-base-patch16-224-in21k
Vision Transformer (base-sized model) Vision Transformer (ViT) model pre-trained on ImageNet-21k (14 million images, 21,843 classes) at resolution 224x224. It was introduced in the paper An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale by Dosovitskiy et al. and first released in this repository. However, the weights were converted from the timm repository by Ross Wightman, who already converted the weights from JAX to PyTorch. Credits go to him. Disclaimer: The team releasing ViT did not write a model card for this model so this model card has been written by the Hugging Face team.
Para poder trabajar éxitosamente con transfer learning
y PyTorch
y Transformers
vamos a necesitar definir los siguientes puntos:
model
=model,args
=training_args,data_collator
=collate_fn,compute_metrics
=compute_metrics,train_dataset
=prepared_ds.train,eval_dataset
=prepared_ds.validation,tokenizer
=feature_extractor
Cada uno de estos puntos los estaremos definiendo en las próximas clases, y cada clase tendrá su código especializado, adicionalmente la clase 2 tendrá todo el código de la clase 1, pero solo explicación de la clase más reciente y así sucesivamente.
El primer paso de nuestra tarea será descargar el dataset de beans
, para ello necesitamos instalar datasets
y transformers
bibliotecas de
hugging face
que estaremos usando a lo largo de este mini proyecto.
El código de esta sección lo puedes encontrar Aquí
pip install datasets transformers
El primer paso de nuestro journey
será descargar el dataset beans
con ayuda de datasets
from datasets import load_dataset
ds = load_dataset("beans")
print(ds)
La primera vez que ejecutes el código descargara todos los datos necesarios de las particiones de train
, validation
, test
Downloading builder script: 100%|██████████| 3.61k/3.61k [00:00<00:00, 5.43MB/s]
Downloading metadata: 100%|██████████| 2.24k/2.24k [00:00<00:00, 1.44MB/s]
Downloading readme: 100%|██████████| 4.75k/4.75k [00:00<00:00, 7.66MB/s]
Downloading data: 100%|██████████| 144M/144M [00:16<00:00, 8.68MB/s]
Downloading data: 100%|██████████| 18.5M/18.5M [00:02<00:00, 6.33MB/s]
Downloading data: 100%|██████████| 17.7M/17.7M [00:02<00:00, 5.91MB/s]
Downloading data files: 100%|██████████| 3/3 [00:30<00:00, 10.14s/it]
Extracting data files: 100%|██████████| 3/3 [00:00<00:00, 7.72it/s]
100%|██████████| 3/3 [00:00<00:00, 1621.30it/s]
Respuesta esperada:
DatasetDict({
train: Dataset({
features: ['image_file_path', 'image', 'labels'],
num_rows: 1034
})
validation: Dataset({
features: ['image_file_path', 'image', 'labels'],
num_rows: 133
})
test: Dataset({
features: ['image_file_path', 'image', 'labels'],
num_rows: 128
})
})
Conozcamos un poco las imágenes del train set
, como ya sabemos nuestra variable ds
es un diccionario, entonces podemos
acceder a sus elementos de la misma forma que lo hacemos en python
en este caso si queremos acceder al conjunto de train
basta con pedir el valor de la llave train
-> ds["train"]
ahora veamos un ejemplo en concreto:
import matplotlib.pyplot as plt
# Mostremos un ejemplo:
ex = ds["train"][400]
print(ex)
test_image = ex["image"]
plt.imshow(test_image)
plt.savefig("ejemplo.png")
plt.close()
Respuesta esperada:
{'image_file_path': '/home/ichcanziho/.cache/huggingface/datasets/downloads/extracted/e2e91becfe5d52af03524711c3b7bb9eb49cc8c6672e84d1f38bda746b2df8ef/train/bean_rust/bean_rust_train.148.jpg',
'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=500x500 at 0x7F882B754F70>,
'labels': 1}
Podemos observar como cada uno de los elementos del train set
tiene 3 valores:
image_file_path:
La dirección en memoria de donde se encuentra la imagenimage:
La imagen en formatoPIL
labels:
La etiqueta de clasificación de la imagen
Sin embargo, a nosotros como seres humanos nos parece mucho más como y entendible leer a qué clase corresponde dicha hoja y no únicamente el elemento numérico que lo representa, entonces primero, conozcamos cuales son las clases disponibles:
labels = ds["train"].features["labels"]
print(labels)
Respuesta esperada:
ClassLabel(names=['angular_leaf_spot', 'bean_rust', 'healthy'], id=None)
La variable labels
cuanta con una función muy útil llamada int2str
que a partir de un número regresa la etiqueta correspondiente:
label_name = labels.int2str(ex["labels"])
print(label_name)
Respuesta esperada:
bean_rust
Recordemos que estamos usando un modelo pre-entrenado
es por ello que necesitamos hacer que nuestras imágenes para el
fine tuning
correspondan con las mismas caracterísitcas con las que fue implementado el modelo base, para ello necesitamos
implementar un Feature Extractor
que nos ayude a hacer que las imágenes de nuestro problema correspondan con las necesidades
del modelo base. Para nuestra suerte, el modelo google/vit-base-patch16-224-in21k
ya cuanta con un Feature Extractor
que podremos usar de la siguiente manera:
El código de esta clase lo puedes encontrar Aquí
En esta clase necesitamos tener instalado torch
estás son las instrucciones:
Nota: recordemos que este repositorio fue creado con
linux
como sistema operativo.
conda install mkl mkl-include
# CUDA only: Add LAPACK support for the GPU if needed
conda install -c pytorch magma-cuda110 # or the magma-cuda* that matches your CUDA version from https://anaconda.org/pytorch/repo
pip install torch
Hasta este momento, el código que aprendimos en la clase pasada fue el siguiente:
from datasets import load_dataset
ds = load_dataset("beans")
ex = ds["train"][400]
test_image = ex["image"]
labels = ds["train"].features["labels"]
label_name = labels.int2str(ex["labels"])
Tener en cuenta esto, porque más adelante estaremos usando ds
y test_image
Primero debemos obtener nuestro Feature Extractor
para problemas de Vision
es por ello que usaremos la clase
ViFeatureExtractor
, después le diremos que lo tome desde un modelo pre-entrenado
y vamos a observar sus características:
from transformers import ViTFeatureExtractor
base_model_url = 'google/vit-base-patch16-224-in21k'
feature_extractor = ViTFeatureExtractor.from_pretrained(base_model_url)
print(feature_extractor)
Respuesta esperada:
ViTFeatureExtractor {
"do_normalize": true,
"do_rescale": true,
"do_resize": true,
"image_mean": [
0.5,
0.5,
0.5
],
"image_processor_type": "ViTFeatureExtractor",
"image_std": [
0.5,
0.5,
0.5
],
"resample": 2,
"rescale_factor": 0.00392156862745098,
"size": {
"height": 224,
"width": 224
}
}
Observemos que el proceso de Feature Extractor
de este modelo se basa principalmente en normalizar las imágenes y re-escalarlas
a unas dimensiones de 224x224
.
Veamos cómo podríamos limpiar
una sola imagen de nuestro set de train
. El objetivo será pasarle una imagen de prueba y
regresar la imagen ya normalizada con el feature_extractor
en formato de PyTorch - Tensor
feature_test = feature_extractor(test_image, return_tensors="pt")
print(feature_test)
print(feature_test.keys())
print(feature_test["pixel_values"].shape)
Respuesta esperada
{'pixel_values': tensor([[[[ 0.7882, 0.6706, 0.7098, ..., -0.1922, -0.1294, -0.1765],
[ 0.7098, 0.6000, 0.6784, ..., -0.2863, -0.1608, -0.1608],
[ 0.4902, 0.3882, 0.4667, ..., -0.1922, -0.0196, 0.0275],
...,
[ 0.3804, 0.5294, 0.4824, ..., -0.8275, -0.8196, -0.8039],
[ 0.0902, 0.3725, 0.3804, ..., -0.8667, -0.8431, -0.8510],
[-0.0510, 0.2784, 0.3176, ..., -0.8588, -0.8275, -0.8353]],
[[ 0.4902, 0.3490, 0.3804, ..., -0.6078, -0.5373, -0.5843],
[ 0.3569, 0.2000, 0.3176, ..., -0.7255, -0.6000, -0.5922],
[ 0.0431, -0.0902, 0.0588, ..., -0.6392, -0.4745, -0.4275],
...,
[-0.2235, -0.0510, -0.0902, ..., -0.9686, -0.9529, -0.9294],
[-0.5059, -0.2078, -0.1922, ..., -0.9922, -0.9922, -1.0000],
[-0.6471, -0.2941, -0.2471, ..., -0.9843, -0.9765, -0.9843]],
[[ 0.4196, 0.2706, 0.3020, ..., -0.7098, -0.6392, -0.6863],
[ 0.2314, 0.0824, 0.2078, ..., -0.8039, -0.6627, -0.6627],
[-0.1137, -0.2314, -0.0824, ..., -0.7020, -0.5373, -0.4980],
...,
[-0.2784, -0.1373, -0.2000, ..., -0.9529, -0.9529, -0.9451],
[-0.6000, -0.3098, -0.3176, ..., -0.9765, -0.9843, -0.9922],
[-0.7569, -0.4118, -0.3804, ..., -0.9765, -0.9686, -0.9686]]]])}
dict_keys(['pixel_values'])
torch.Size([1, 3, 224, 224])
Ahora podemos comprobar que nuestra test_image
se ha transformado en un diccionario que tiene una sola key pixel_values
y que este contiene un tensor
con unas dimensiones de [1, 3, 224, 224]
lo cual significa que es una sola imagen de 224x224 en formato RGB
Sin embargo, nosotros sabemos que para entrenar a un modelo de Deep Learning sin importar cuál sea la biblioteca que estemos
usando, necesitamos al menos 2 ingredientes, imágenes y etiquetas. En este momento ya tenemos una forma de transformar él dataset
a un formato
entendible por el modelo de nuestra red neuronal
sin embargo, debemos agregar a cuál clase pertenece cada imagen, esto es
sumamente sencillo si tenemos en cuenta que las imágenes pre-procesadas con feature_extractor
en realidad son diccionarios, esto
nos permite simplemente añadirles una nueva key
llamada label
y ponerle su valor correspondiente, esto se hace de la siguiente manera:
La siguiente función recibe un example
de alguna de nuestras particiones que recordemos que a su vez son diccionarios que contienen:
[image_file_path, image, labels], sin embargo, ahora solo nos interesa enfocarnos en las últimas dos claves. Esta funcion se
encarga de recibir esta muestra y transformarla con feature_extractor
y adjuntarle su example["labels"]
def process_example(example):
inputs = feature_extractor(example["image"], return_tensors="pt")
inputs["labels"] = example["labels"]
return inputs
Veamos un ejemplo, primero seleccionemos una muestra, digamos el train
número 10:
preprocess_test = ds["train"][10]
print(preprocess_test)
Respuesta esperada
{'image_file_path': '/home/ichcanziho/.cache/huggingface/datasets/downloads/extracted/e2e91becfe5d52af03524711c3b7bb9eb49cc8c6672e84d1f38bda746b2df8ef/train/angular_leaf_spot/angular_leaf_spot_train.107.jpg', 'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=500x500 at 0x7F035C2589A0>, 'labels': 0}
{'pixel_values': tensor([[[[ 0.0275, -0.0196, 0.0196, ..., 0.0275, 0.2157, -0.0039],
[-0.0353, 0.0902, -0.0275, ..., 0.2000, 0.3569, 0.1922],
[-0.0902, 0.1216, 0.0353, ..., 0.1686, 0.3333, 0.3098],
...,
[-0.0353, -0.2784, -0.4353, ..., -0.2863, -0.1843, -0.3882],
[ 0.2157, -0.1765, -0.3804, ..., -0.1294, -0.1608, -0.3647],
[ 0.3020, -0.0275, -0.3255, ..., -0.2471, -0.1686, -0.3333]],
[[-0.3412, -0.3569, -0.2627, ..., -0.3176, -0.1373, -0.3412],
[-0.4039, -0.2549, -0.3255, ..., -0.1373, -0.0118, -0.1843],
[-0.4745, -0.2314, -0.2863, ..., -0.1922, -0.0275, -0.0510],
...,
[ 0.2000, 0.0431, -0.0196, ..., -0.5373, -0.3569, -0.5216],
[ 0.3412, 0.0745, 0.0118, ..., -0.4039, -0.3490, -0.4980],
[ 0.3725, 0.1922, 0.0588, ..., -0.5216, -0.3647, -0.4745]],
[[-0.5059, -0.5294, -0.4824, ..., -0.4431, -0.2784, -0.4745],
[-0.5686, -0.4431, -0.5294, ..., -0.3333, -0.1922, -0.3490],
[-0.6314, -0.4196, -0.4588, ..., -0.3647, -0.2157, -0.2314],
...,
[-0.5686, -0.7176, -0.7333, ..., -0.6941, -0.5137, -0.6235],
[-0.3020, -0.6314, -0.6863, ..., -0.5765, -0.5137, -0.6078],
[-0.1843, -0.4667, -0.6235, ..., -0.7176, -0.5373, -0.5922]]]]),
'labels': 0}
Excelente, nosotros ya esperábamos ver un formato de 3 keys
, pero ahora vamos a transformar esta imagen en un formato útil:
preprocess_test = process_example(preprocess_test)
print(preprocess_test)
print(preprocess_test["pixel_values"].shape)
{'pixel_values': tensor([[[[ 0.0275, -0.0196, 0.0196, ..., 0.0275, 0.2157, -0.0039],
[-0.0353, 0.0902, -0.0275, ..., 0.2000, 0.3569, 0.1922],
[-0.0902, 0.1216, 0.0353, ..., 0.1686, 0.3333, 0.3098],
...,
[-0.0353, -0.2784, -0.4353, ..., -0.2863, -0.1843, -0.3882],
[ 0.2157, -0.1765, -0.3804, ..., -0.1294, -0.1608, -0.3647],
[ 0.3020, -0.0275, -0.3255, ..., -0.2471, -0.1686, -0.3333]],
[[-0.3412, -0.3569, -0.2627, ..., -0.3176, -0.1373, -0.3412],
[-0.4039, -0.2549, -0.3255, ..., -0.1373, -0.0118, -0.1843],
[-0.4745, -0.2314, -0.2863, ..., -0.1922, -0.0275, -0.0510],
...,
[ 0.2000, 0.0431, -0.0196, ..., -0.5373, -0.3569, -0.5216],
[ 0.3412, 0.0745, 0.0118, ..., -0.4039, -0.3490, -0.4980],
[ 0.3725, 0.1922, 0.0588, ..., -0.5216, -0.3647, -0.4745]],
[[-0.5059, -0.5294, -0.4824, ..., -0.4431, -0.2784, -0.4745],
[-0.5686, -0.4431, -0.5294, ..., -0.3333, -0.1922, -0.3490],
[-0.6314, -0.4196, -0.4588, ..., -0.3647, -0.2157, -0.2314],
...,
[-0.5686, -0.7176, -0.7333, ..., -0.6941, -0.5137, -0.6235],
[-0.3020, -0.6314, -0.6863, ..., -0.5765, -0.5137, -0.6078],
[-0.1843, -0.4667, -0.6235, ..., -0.7176, -0.5373, -0.5922]]]]), 'labels': 0}
torch.Size([1, 3, 224, 224])
Excelente ahora podemos ver como nuestro ejemplo ya es un dato útil, un diccionario que contiene dos llaves: pixel_values
y labels
y no solo eso, la imagen ya está en formato de tensor
y tiene un shape
de [1, 3, 224, 224]
, hasta este punto hemos logrado
exitosamente transformar un ejemplo del train
en un dato listo para ser entrenado por una red neuronal.
Sin embargo, nos gustaría tener una función que NO solo funcione para una unica imagen sino para un lote
de imágenes, eso es
sumamente sencillo, simplemente se trata de repetir el proceso para una lista de imágenes, creemos la función transform
que recibirá un batch
de ejemplos en lugar de uno solo.
def transform(example_batch):
inputs = feature_extractor([x for x in example_batch["image"]], return_tensors="pt")
inputs["labels"] = example_batch["labels"]
return inputs
Y ahora podemos usar un método sumamente útil de los datasets
de hugging face
llamado: wit_transform(function)
que recibe
como parámetro de entrada una función de transformación, en este caso la que acabamos de crear.
print("batch")
clean = ds.with_transform(transform)
print(clean["train"][0:2])
print(clean["train"][0:2]["pixel_values"].shape)
Respuesta esperada:
batch
{'pixel_values': tensor([[[[-0.5686, -0.5686, -0.5608, ..., -0.0275, 0.1843, -0.2471],
[-0.6078, -0.6000, -0.5765, ..., -0.0353, -0.0196, -0.2627],
[-0.6314, -0.6314, -0.6078, ..., -0.2314, -0.3647, -0.2235],
...,
[-0.5373, -0.5529, -0.5843, ..., -0.0824, -0.0431, -0.0902],
[-0.5608, -0.5765, -0.5843, ..., 0.3098, 0.1843, 0.1294],
[-0.5843, -0.5922, -0.6078, ..., 0.2627, 0.1608, 0.2000]],
[[-0.7098, -0.7098, -0.7490, ..., -0.3725, -0.1608, -0.6000],
[-0.7333, -0.7333, -0.7569, ..., -0.3647, -0.3255, -0.5686],
[-0.7490, -0.7490, -0.7725, ..., -0.5373, -0.6549, -0.5373],
...,
[-0.7725, -0.7804, -0.8196, ..., -0.2235, -0.0353, 0.0824],
[-0.7961, -0.8118, -0.8118, ..., 0.1922, 0.3098, 0.3725],
[-0.8196, -0.8196, -0.8275, ..., 0.0824, 0.2784, 0.3961]],
[[-0.9922, -0.9922, -1.0000, ..., -0.5451, -0.3569, -0.7255],
[-0.9922, -0.9922, -1.0000, ..., -0.5529, -0.5216, -0.7176],
[-0.9843, -0.9922, -1.0000, ..., -0.6549, -0.7569, -0.6392],
...,
[-0.8431, -0.8588, -0.8980, ..., -0.5765, -0.5529, -0.5451],
[-0.8588, -0.8902, -0.9059, ..., -0.2000, -0.2392, -0.2627],
[-0.8824, -0.9059, -0.9216, ..., -0.2549, -0.2000, -0.1216]]],
[[[-0.5137, -0.4902, -0.4196, ..., -0.0275, -0.0039, -0.2157],
[-0.4902, -0.4667, -0.4196, ..., -0.0588, -0.0118, -0.2392],
[-0.4588, -0.4902, -0.5137, ..., -0.0745, 0.0039, -0.3020],
...,
[-0.4980, -0.4980, -0.5294, ..., -0.2000, -0.2157, -0.3882],
[-0.5451, -0.5294, -0.5216, ..., -0.1922, -0.1922, -0.3882],
[-0.5216, -0.5373, -0.5451, ..., -0.1294, -0.1529, -0.2627]],
[[-0.1843, -0.2000, -0.1529, ..., 0.2157, 0.2078, -0.0902],
[-0.1686, -0.1686, -0.1529, ..., 0.1922, 0.2235, -0.0902],
[-0.1529, -0.2000, -0.2392, ..., 0.1686, 0.2549, -0.1294],
...,
[-0.7725, -0.7569, -0.7569, ..., -0.4196, -0.4588, -0.6471],
[-0.7961, -0.7804, -0.7647, ..., -0.4196, -0.4510, -0.6627],
[-0.7725, -0.7961, -0.8039, ..., -0.3725, -0.4196, -0.5451]],
[[-0.7569, -0.8510, -0.8353, ..., -0.3255, -0.2706, -0.5608],
[-0.7804, -0.8431, -0.8118, ..., -0.3490, -0.2706, -0.5608],
[-0.8118, -0.8588, -0.8510, ..., -0.3647, -0.2314, -0.5373],
...,
[-0.5216, -0.5137, -0.5294, ..., -0.2471, -0.2627, -0.4431],
[-0.5529, -0.5373, -0.5216, ..., -0.2235, -0.2235, -0.4431],
[-0.5294, -0.5529, -0.5608, ..., -0.1686, -0.1922, -0.3333]]]]), 'labels': [0, 0]}
torch.Size([2, 3, 224, 224])
Excelente, hemos logrado convertir nuestras imágenes del dataset de forma éxitosa en un formato listo para usar en Deep Learning
pero como último paso y cereza del pastel vamos a crear una función que reciba justamente este diccionario y cree un único tensor
uno después de otro.
Los data collators, o recopiladores de datos, son objetos que forman batches utilizando una lista de ejemplos de nuestros datasets. Para poder generar los batches, los data collators pueden aplicar algún procesamiento (como padding en los ejemplos con texto).
Definimos una función, collate_fn, que fungirá como nuestro data collator. Devolverá un diccionario por cada batch. Recibirá un batch de datos que luego serán procesadas.
Los batches llegan como listas de dicts. Cada dict tiene los label y pixel_values de sus respectivos ejemplos, por lo que puedes simplemente desempaquetarlos y apilarlos en tensores de batches. torch.stack nos permite concatenar (pegar) tensores.
import torch
def collate_fn(batch):
return {
"pixel_values": torch.stack([x["pixel_values"] for x in batch]),
"labels": torch.tensor([x["labels"] for x in batch])
}
Excelente, hemos creado perfectamente nuestra función collate
que usaremos más adelante para entrenar a nuestro modelo.
Si has llegado hasta aquí con un entendimiento de cada parte del código entonces estás listo para continuar con la creación del entrenador del modelo.
El código de esta clase lo puedes encontrar Aquí
Hasta este momento nuestro código limpio y funcional, es el siguiente:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
from datasets import load_dataset
from transformers import ViTFeatureExtractor
import torch
def collate_fn(batch):
return {
"pixel_values": torch.stack([x["pixel_values"] for x in batch]),
"labels": torch.tensor([x["labels"] for x in batch])
}
def transform(example_batch):
inputs = feature_extractor([x for x in example_batch["image"]], return_tensors="pt")
inputs["labels"] = example_batch["labels"]
return inputs
if __name__ == '__main__':
ds = load_dataset("beans")
labels = ds["train"].features["labels"].names
print(labels)
base_model_url = 'google/vit-base-patch16-224-in21k'
feature_extractor = ViTFeatureExtractor.from_pretrained(base_model_url)
clean = ds.with_transform(transform)
Asi que continuemos con los demás puntos de esta clase:
En esta clase nuestro objetivo es definir el resto de los argumentos necesarios para Trainer.
Y debemos comenzar con definir la métrica de evaluación que usaremos para nuestro modelo:
import numpy as np
from datasets import load_metric
metric = load_metric("accuracy")
def compute_metrics(prediction):
return metric.compute(predictions=np.argmax(prediction.predictions, axis=1), references=prediction.label_ids)
Esto parece un poco más engorroso de lo que realmente es, pero si prestamos atención sigue siendo como se computa cualquier otra
métrica de ML como en scikit-learn, básicamente load_metric
tiene varias métricas disponibles entre ellas se encuentra:
['accuracy',
'bertscore',
'bleu',
'bleurt',
'brier_score',
'cer',
'character',
...
ition_math',
ypchang/sklearn_proxy',
'yulong-me/yl_metric',
'yzha/ctc_eval',
'zbeloki/m2']
Para conocer toda la lista de métricas puedes ejecutar:
import datasets
datasets.list_metrics()
Puedes conocer más información en: https://huggingface.co/docs/datasets/how_to_metrics
La función metric.compute(predictions, references)
recibe 2 parámetros, el primero es el valor de las predicciones, que en nuestro caso
las predicciones serán una lista de probabilidades, por eso usamos np.argmax
para obtener el resultado más probable, en este caso nuestra función:
compute_metrics
recibe una prediction
que cuenta con 2 valores prediction.prediction
y prediction.label_ids
Carguemos el modelo pre-entrenado. Agregaremos num_labels
para que el modelo cree un encabezado de clasificación con el número correcto de etiquetas. También incluiremos las asignaciones id2label
y label2id
para tener etiquetas legibles por humanos en el widget del Hub.
from transformers import ViTForImageClassification
labels = ds["train"].features["labels"].names
model = ViTForImageClassification.from_pretrained(
base_model_url,
num_labels=len(labels),
id2label={str(i): c for i, c in enumerate(labels)},
label2id={c: str(i) for i,c in enumerate(labels)}
)
Hasta este momento, solo habíamos usado él base_model_url
para obtener su feature extractor
pero NO habiamos descargado
el modelo en sí mismo, esto lo logramos con: ViForImageClassification.from_pretrained
Respuesta esperada:
Downloading (…)lve/main/config.json: 100%|██████████| 502/502 [00:00<00:00, 441kB/s]
Downloading pytorch_model.bin: 100%|██████████| 346M/346M [00:56<00:00, 6.09MB/s]
Some weights of the model checkpoint at google/vit-base-patch16-224-in21k were not used when initializing ViTForImageClassification: ['pooler.dense.bias', 'pooler.dense.weight']
- This IS expected if you are initializing ViTForImageClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing ViTForImageClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Some weights of ViTForImageClassification were not initialized from the model checkpoint at google/vit-base-patch16-224-in21k and are newly initialized: ['classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
Excelente, hemos logrado descargar exitosamente el modelo y ya hemos cambiado la última capa del mismo por una capa de 3 salidas
correspondientes a nuestro número de labels
y el propio modelo nos dice que se ha añadido una nueva última capa y que esta
debería ser TRAIN
para ser capaz de ser usada en predictions e inferencias.
Lo último que se necesita antes de eso es establecer la configuración de entrenamiento definiendo TrainingArguments.
Pero antes de eso vamos a hacer login
en hugging face
huggingface-cli login
Respuesta esperada:
_| _| _| _| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _|_|_|_| _|_| _|_|_| _|_|_|_|
_| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
_|_|_|_| _| _| _| _|_| _| _|_| _| _| _| _| _| _|_| _|_|_| _|_|_|_| _| _|_|_|
_| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
_| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|
To login, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens .
Token:
Nos pedirá un token de autentificación, debemos entrar al link que nos ofrece: https://huggingface.co/settings/tokens y copiar el token que nos generó automáticamente.
La mayoría de estos se explican por sí mismos, pero uno que es bastante importante aquí es remove_unused_columns=False. Este eliminará cualquier función que no utilice la función de llamada del modelo. De forma predeterminada es True porque, por lo general, es ideal eliminar las columnas de funciones no utilizadas, lo que facilita el desempaquetado de las entradas en la función de llamada del modelo. Pero, en nuestro caso, necesitamos las funciones no utilizadas ('imagen' en particular) para crear 'pixel_values'.
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./platzi-vit-model-gabriel-ichcanziho",
evaluation_strategy="steps",
num_train_epochs=4,
push_to_hub_organization="platzi",
learning_rate=2e-4,
remove_unused_columns=False,
push_to_hub=True,
load_best_model_at_end=True,
)
Finalmente, definamos nuestro Trainer
from transformers import Trainer
trainer = Trainer(
model=model,
args=training_args,
data_collator=collate_fn,
compute_metrics=compute_metrics,
train_dataset=clean["train"],
eval_dataset=clean["validation"],
tokenizer=feature_extractor
)
Excelente, hasta este momento ya hemos definido TODO lo necesario para empezar a hacer fine-tuning
y entrenar a nuestro modelo
con base en el modelo de Google pero adaptándolo a nuestras necesidades. Ha sido un proceso un poco engorroso, puesto que en
TensorFlow
estos pasos al ser en una capa de abstracción más baja se tiene mayor control sobre lo que se hace y se siente más
cómo definir las cosas en lugar de estar usando funciones que ya existen para todo; sin embargo, el proceso ha sido bastante similar hasta este momento.
Estamos a punto de acabar con el tutorial, ya solo es necesario poner a entrenar nuestro modelo, guardar el mejor resultado
y finalmente evaluar los resultados con el dataset de test
.
El código completo de esta sección lo puedes encontrar Aquí
Antes de continuar con la última parte de este tutorial, veamos todo el código limpio que hemos creado hasta el momento:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
from datasets import load_dataset
from datasets import load_metric
from transformers import ViTFeatureExtractor
from transformers import ViTForImageClassification
from transformers import TrainingArguments
from transformers import Trainer
import numpy as np
import torch
def collate_fn(batch):
return {
"pixel_values": torch.stack([x["pixel_values"] for x in batch]),
"labels": torch.tensor([x["labels"] for x in batch])
}
def transform(example_batch):
inputs = feature_extractor([x for x in example_batch["image"]], return_tensors="pt")
inputs["labels"] = example_batch["labels"]
return inputs
def compute_metrics(prediction):
return metric.compute(predictions=np.argmax(prediction.predictions, axis=1), references=prediction.label_ids)
if __name__ == '__main__':
ds = load_dataset("beans")
labels = ds["train"].features["labels"].names
base_model_url = 'google/vit-base-patch16-224-in21k'
feature_extractor = ViTFeatureExtractor.from_pretrained(base_model_url)
clean = ds.with_transform(transform)
metric = load_metric("accuracy")
model = ViTForImageClassification.from_pretrained(
base_model_url,
num_labels=len(labels),
id2label={str(i): c for i, c in enumerate(labels)},
label2id={c: str(i) for i, c in enumerate(labels)}
)
training_args = TrainingArguments(
output_dir="./platzi-vit-model-gabriel-ichcanziho",
evaluation_strategy="steps",
num_train_epochs=4,
push_to_hub_organization="platzi",
learning_rate=2e-4,
remove_unused_columns=False,
push_to_hub=True,
load_best_model_at_end=True,
)
trainer = Trainer(
model=model,
args=training_args,
data_collator=collate_fn,
compute_metrics=compute_metrics,
train_dataset=clean["train"],
eval_dataset=clean["validation"],
tokenizer=feature_extractor
)
Podemos observar una secuencia muy lógica entre todas las funciones que hemos creado, y vemos como todas las bibliotecas que hemos importado tienen una función muy en concreto dentro de nuestro código, ahora para terminar este proceso solo falta empezar a entrenar el modelo y evaluar los resultados finales:
train_results = trainer.train()
trainer.save_model()
trainer.log_metrics("train", train_results.metrics)
trainer.save_metrics("train", train_results.metrics)
Resultado esperado:
{'loss': 0.1373, 'learning_rate': 7.692307692307694e-06, 'epoch': 3.85}
{'eval_loss': 0.014603369869291782, 'eval_accuracy': 0.9924812030075187, 'eval_runtime': 1.5139, 'eval_samples_per_second': 87.852, 'eval_steps_per_second': 11.229, 'epoch': 3.85}
{'train_runtime': 130.2439, 'train_samples_per_second': 31.756, 'train_steps_per_second': 3.993, 'train_loss': 0.13224817431316926, 'epoch': 4.0}
***** train metrics *****
epoch = 4.0
train_loss = 0.1322
train_runtime = 0:02:10.24
train_samples_per_second = 31.756
train_steps_per_second = 3.993
Evaluamos en nuestro split
de test
:
metrics = trainer.evaluate(clean["test"])
trainer.log_metrics("eval", metrics)
trainer.save_metrics("eval", metrics)
Resultado esperado:
***** eval metrics *****
epoch = 4.0
eval_accuracy = 0.9609
eval_loss = 0.1104
eval_runtime = 0:00:01.46
eval_samples_per_second = 87.12
eval_steps_per_second = 10.89
Felicidades, nuestro modelo ha logrado un 96% De Accuracy en predecir entre las 3 categorías de hojas de frijol.
Puedes encontrar nuestro modelo en el Hub de Hugging Face en:
https://huggingface.co/platzi/platzi-vit-model-gabriel-ichcanziho
En lineamientos generales este proyecto es sumamente similar al proyecto pasado, pero enfocado a trabajar con texto. Este mini proyecto estará dividido en las mismas 4 secciones que el proyecto anterior.
Vamos a trabajar con el dataset glue Expecificamente con el subset MRPC
.
El tema de MRPC
es dada dos oraciones sentence1, sentence2
etiquetar si ambos textos son equivalentes o no lo son.
Cómo Feature_Extractor
y Pretrained Model
vamos a usar distilroberta-base el cual es un modelo
más pequeño y rápido que un modelo BERT
convencional.
Como primer paso debemos descargar el dataset que vamos a utilizar para este proyecto:
El códido completo de esta clase lo puedes encontrar Aquí
from datasets import load_dataset
ds = load_dataset("glue", "mrpc")
La primera vez que corramos el código anterior va a descargar el dataset:
Downloading builder script: 100%|██████████| 28.8k/28.8k [00:00<00:00, 319kB/s]
Downloading metadata: 100%|██████████| 28.7k/28.7k [00:00<00:00, 312kB/s]
Downloading readme: 100%|██████████| 27.9k/27.9k [00:00<00:00, 280kB/s]
Downloading and preparing dataset glue/mrpc to /home/ichcanziho/.cache/huggingface/datasets/glue/mrpc/1.0.0/dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad...
...
Downloading data files: 100%|██████████| 3/3 [00:02<00:00, 1.24it/s]
100%|██████████| 3/3 [00:00<00:00, 1703.85it/s]
Dataset glue downloaded and prepared to /home/ichcanziho/.cache/huggingface/datasets/glue/mrpc/1.0.0/dacbe3125aa31d7f70367a07a8a9e72a5a0bfeb5fc42e75c9db75b96da6053ad. Subsequent calls will reuse this data.
Una vez descargado el dataset podemos observar que su estructura es la siguiente:
print(ds)
Respuesta esperada:
DatasetDict({
train: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
validation: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 408
})
test: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 1725
})
})
Nuestro dataset contiene 3668
ejemplos de entrenamiento, 408
ejemplos de validación y 1725
ejemplos de prueba.
Cada uno de ellos contiene 2 oraciones una etiqueta y un índice.
Mostremos un ejemplo del subset de train
# Mostremos un ejemplo:
ejemplo = ds["train"][400]
print(ejemplo)
Respuesta esperada:
{'sentence1': 'U.S. Agriculture Secretary Ann Veneman , who announced Tuesdays ban , also said Washington would send a technical team to Canada to help .',
'sentence2': "U.S. Agriculture Secretary Ann Veneman , who announced yesterday 's ban , also said Washington would send a technical team to Canada to assist in the Canadian situation .",
'label': 1,
'idx': 446}
Recordemos que a nosotros como humanos nos gusta leer etiquetas con un nombre y no con un numero, entonces conozcamos cuáles son los nombres de las labels
:
labels = ds["train"].features["label"]
print(labels)
Respuesta esperada:
ClassLabel(names=['not_equivalent', 'equivalent'], id=None)
Excelente, ya sabemos que los datos pueden ser not_equivalent
o equivalent
, afortunadamente, el objeto labels
cuenta con un
simple método int2str
que convierte un número a su representación de texto, entonces nuestro ejemplo que tiene un label 1
podemos acceder
al nombre de la etiqueta de la siguiente manera:
# Nombre de la etiqueta
label_name = labels.int2str(ejemplo["label"])
print(label_name)
Respuesta esperada:
equivalent
Perfecto, hasta este momento ya estamos familiarizados con el dataset que estaremos utilizando y vemos como hasta este punto el proyecto es sumamente parecido al proyecto pasado, continuemos en la siguiente clase.
Al igual que en el ejemplo de Computer Vision
, se debe hacer un preprocesamiento para trabajar con el texto de entrada de modo
que tenga la forma correcta que está buscando nuestro modelo pre-entrenado
específicamente en NLP
El texto NO puede ser
clasificado directamente, debe ser convertido en una representación numerica para poder trabajar con ellos. A este proceso de
transformación se le conoce como: Word Embeddings
Para conocer más acerca de este tema te recomiendo ver el siguiente video:
Intro al NLP ¿Qué es un EMBEDDING?. Ya hemos trabajado con
este concepto en otros cursos anteriores de esta ruta de Deep Learning
si quieres leer un poco más al respecto te recomiendo ver:
Variables Estadísticas
El código de esta clase lo puedes encontrar Aquí
En general el objetivo de este paso es pre-procesar los datos de texto de nuestro dataset en una representación vectorial que sea compatible con
el modelo que vas a usar. Para ello vamos a usar el propio Tokenizer
del modelo que vamos a utilizar en este proyecto:
Primero, tengamos en cuenta que vamos a usar el modelo distilroberta-base
, vamos a utilizar AutoTokenizer
para obtener el
tokenizador correspondiente a nuestro modelo:
from transformers import AutoTokenizer
base_model_url = "distilroberta-base"
tokenizer = AutoTokenizer.from_pretrained(base_model_url)
ejemplo = ds["train"][400]["sentence1"]
print(ejemplo)
print(tokenizer(ds["train"][400]["sentence1"]))
Respuesta esperada:
U.S. Agriculture Secretary Ann Veneman , who announced Tuesdays ban , also said Washington would send a technical team to Canada to help .
{'input_ids': [0, 791, 4, 104, 4, 8004, 1863, 3921, 10336, 5649, 2156, 54, 585, 20423, 7033, 2020, 2156, 67, 26, 663, 74, 2142, 10, 3165, 165, 7, 896, 7, 244, 479, 2], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
¿Qué significa cada uno de los valores que nos retorna el tokenizador?
input_ids
es la traducción de palabras a números.attention_mask
es un tensor con la misma forma que input_ids, pero lleno de 0 y 1: los 1 indican que se debe atender a los tokens correspondientes y los 0 indican que no se deben atender. Es decir, deben ser ignorados por el modelo.token_type_ids
dice al modelo qué parte de la entrada es la primera oración y cuál es la segunda oración.
Existen otros modelos más complejos como: "bert-base-uncased"
cuyo tokenizador contiene un método especializado en transformar
de regreso los inputs_ids
en el texto original: tokenizer.convert_ids_to_tokens(inputs["input_ids"])
En este caso, nuestro modelo
ligero NO contiene dicho método, pero es bueno tener en cuenta que otros modelos sí tienen esta capacidad.
Creemos una función Tokenizadora:
def tokenize_fn(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
Afortunadamente, nuestro objeto tokenizer
permite recibir multiples parámetros
separados por ,
esto me permite transformar
nuestras 2 oraciones de ejemplo al mismo tiempo. El parámetro truncatoion = True
significa que oraciones sumamente largas serán
recortadas a unas más pequeñas de un tamaño predefinido por el modelo.
Finalmente, vamos a limpiar
todo nuestro dataset, a diferencia del dataset de imágenes que ocupábamos el método ds.with_transform(transform)
y utilizábamos una función transform
para modificar el dataset, como específicamente podemos transformar nuestros datos de entrada a su versión
tokenizada con tokenize_fn
podemos usar el método map
para mapear los parámetros de sentence1
y sentence2
de cada muestra del dataset
Veamos un ejemplo del dataset ya limpio
clean_ds = ds.map(tokenize_fn, batched=True)
print(clean_ds["train"][400])
Respuesta esperada:
{
'sentence1': 'U.S. Agriculture Secretary Ann Veneman , who announced Tuesdays ban , also said Washington would send a technical team to Canada to help .',
'sentence2': "U.S. Agriculture Secretary Ann Veneman , who announced yesterday 's ban , also said Washington would send a technical team to Canada to assist in the Canadian situation .",
'label': 1,
'idx': 446,
'input_ids': [0, 791, 4, 104, 4, 8004, 1863, 3921, 10336, 5649, 2156, 54, 585, 20423, 7033, 2020, 2156, 67, 26, 663, 74, 2142, 10, 3165, 165, 7, 896, 7, 244, 479, 2, 2, 791, 4, 104, 4, 8004, 1863, 3921, 10336, 5649, 2156, 54, 585, 2350, 128, 29, 2020, 2156, 67, 26, 663, 74, 2142, 10, 3165, 165, 7, 896, 7, 3991, 11, 5, 1563, 1068, 479, 2],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
Excelente, ahora nuestro diccionario de una muestra de entrenamiento contiene 6 keys
las 4 originales y 2 adicionales, input_ids
y `attention_mask
Finalmente, a pesar de que él dataset
ya está limpio
al igual que en el ejemplo de Computer Vision
necesitamos definir un Data Collactor
Necesitamos que nuestros tensores tengan una forma rectangular. Es decir que tengan el mismo tamaño cada uno de los ejemplos. Sin embargo, los textos no necesariamente tienen el mismo tamaño.
Para ello usamos el relleno o padding. El padding se asegura de que todas nuestras oraciones tengan la misma longitud al agregar una palabra especial llamada padding token a las oraciones con menos valores. Por ejemplo, si tenemos 10 oraciones con 10 palabras y 1 oración con 20 palabras, el relleno garantizará que todas las oraciones tengan 20 palabras.
Dejamos el argumento de padding del tokenizer vacío en nuestra función de tokenización por ahora. Esto se debe a que rellenar (hacer padding) todas las muestras hasta la longitud máxima del dataset no es eficiente, es mejor rellenar las muestras cuando estamos construyendo un batch, ya que entonces solo necesitamos rellenar hasta la longitud máxima en ese batch, y no la longitud máxima en todo el dataset. ¡Esto puede ahorrar mucho tiempo y potencia de procesamiento cuando las entradas tienen longitudes muy variables!
Usaremos un DataCollator para esto. Rellenemos (hagamos padding) todos los ejemplos con la longitud del elemento más largo del batch. A esta técnica se le conoce como relleno dinámico o dynamic padding.
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
print(data_collator)
Respuesta esperada:
DataCollatorWithPadding(tokenizer=RobertaTokenizerFast(name_or_path='distilroberta-base', vocab_size=50265, model_max_length=512, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>', 'sep_token': '</s>', 'pad_token': '<pad>', 'cls_token': '<s>', 'mask_token': AddedToken("<mask>", rstrip=False, lstrip=True, single_word=False, normalized=False)}), padding=True, max_length=None, pad_to_multiple_of=None, return_tensors='pt')
Perfecto, ya hemos definido exitosamente a nuestro DataCollator
esto nos permite definir a nuestro Trainer
pero esto lo veremos en la siguiente clase.
Hasta este momento el código completo y limpio es el siguiente:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
from datasets import load_dataset
from transformers import AutoTokenizer
from transformers import DataCollatorWithPadding
def tokenize_fn(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
if __name__ == '__main__':
# Descarga de dataset
ds = load_dataset("glue", "mrpc")
labels = ds["train"].features["label"].names
# Descarga de tokenizador de DistilRoberta
base_model_url = "distilroberta-base"
tokenizer = AutoTokenizer.from_pretrained(base_model_url)
clean_ds = ds.map(tokenize_fn, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
En esta clase vamos a definir las funciones que nos faltan para poder crear a nuestro Trainer
El código completo de esta sección lo puedes encontrar Aquí
Vamos a empezar por definir nuestra métrica de éxito para ello vamos a instalar una nueva biblioteca:
pip install evaluate
Esta biblioteca nos va a ayudar con el mismo objetivo que lo hizo from datasets import load_metric
pero en este caso, la métrica que vamos
a utilizar va a estar definida por el propio dataset, en el curso no queda muy claro porque usar está metodología en lugar de la habitual.
En general observamos que la estructura de la función compute_metrics
es sumamente similar a la que ya conociamos, solo que en lugar de usar load_metric
estamos usando evaluate.load()
, en general me pareció más entendible la forma pasada.
import evaluate
import numpy as np
def compute_metrics(eval_pred):
metric = evaluate.load("glue", "mrpc")
logits, labels_ = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels_)
A continuación vamos a crear a nuestro modelo desde un pretrained
como el modelo es una clasificación de texto, lo debemos
importar usando AutoModelForSquencueClassification
, es importante conocer transformers
para conocer todas las clases que
podemos utilizar para cargar modelos pre-entrenados, pero en este curso ya vimos las 2 más populares.
Más adelante cuando hayamos terminado el entrenado del modelo, veremos cuáles son las métricas, y observaremos que el método de evaluate.load() cargo dos métricas ACC y F1 score, algo sumamente interesante y curioso que vale la pena poner como nota.
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(
base_model_url,
num_labels=len(labels),
id2label={str(i): c for i, c in enumerate(labels)},
label2id={c: str(i) for i, c in enumerate(labels)}
)
Ahora podemos definir nuestros argumentos de entrenamiento:
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./platzi-distilroberta-base-mrpc-glue-gabriel-ichcanziho",
evaluation_strategy="steps",
num_train_epochs=4,
push_to_hub_organization="platzi",
push_to_hub=True,
load_best_model_at_end=True
)
Esto fue sumamente parecido al problema de clasificación de imágenes, pero noto que aquí NO pusimos learning_rate=2e-4, remove_unused_columns=False,
en la clase no se explica el porqué de este cambio.
Y para terminar con esta clase, vamos a definir a nuestro trainer
:
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=clean_ds["train"],
eval_dataset=clean_ds["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
Felicidades, ya tenemos TODO listo para empezar a entrenar a nuestro modelo, pero esto lo haremos en la siguiente clase.
Es importante que antes de proceder a evaluar nuestro modelo, hagamos logging a hugging face
:
huggingface-cli login
Respuesta esperada:
_| _| _| _| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _|_|_|_| _|_| _|_|_| _|_|_|_|
_| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
_|_|_|_| _| _| _| _|_| _| _|_| _| _| _| _| _| _|_| _|_|_| _|_|_|_| _| _|_|_|
_| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
_| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|
A token is already saved on your machine. Run `huggingface-cli whoami` to get more information or `huggingface-cli logout` if you want to log out.
Setting a new token will erase the existing one.
To login, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens .
Token:
Add token as git credential? (Y/n) y
Token is valid.
Your token has been saved in your configured git credential helpers (store).
Your token has been saved to /home/ichcanziho/.cache/huggingface/token
Login successful
El código completo de esta sección lo puedes encontrar Aquí
Hasta este momento nuestro código completo y limpio es el siguiente:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
from datasets import load_dataset
from transformers import AutoTokenizer
from transformers import DataCollatorWithPadding
from transformers import AutoModelForSequenceClassification
from transformers import TrainingArguments
from transformers import Trainer
import evaluate
import numpy as np
def tokenize_fn(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
def compute_metrics(eval_pred):
metric = evaluate.load("glue", "mrpc")
logits, labels_ = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels_)
if __name__ == '__main__':
# 1. Descarga de dataset
ds = load_dataset("glue", "mrpc")
labels = ds["train"].features["label"].names
# 2. Descarga de tokenizador de DistilRoberta
base_model_url = "distilroberta-base"
tokenizer = AutoTokenizer.from_pretrained(base_model_url)
# 3. Limpiamos la base de datos original
clean_ds = ds.map(tokenize_fn, batched=True)
# 4. Creamos nuestro DataCollator
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
# 5. Definimos el modelo
model = AutoModelForSequenceClassification.from_pretrained(
base_model_url,
num_labels=len(labels),
id2label={str(i): c for i, c in enumerate(labels)},
label2id={c: str(i) for i, c in enumerate(labels)}
)
# 6. Definimos los argumentos de entrenamiento del modelo
training_args = TrainingArguments(
output_dir="./platzi-distilroberta-base-mrpc-glue-gabriel-ichcanziho",
evaluation_strategy="steps",
num_train_epochs=4,
push_to_hub_organization="platzi",
push_to_hub=True,
load_best_model_at_end=True
)
# 7. Definimos al propio entrenador
trainer = Trainer(
model,
training_args,
train_dataset=clean_ds["train"],
eval_dataset=clean_ds["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
Vamos a agregar las últimas líneas de código para entrenar al modelo y subirlo al HUB de Hugging Face y observar sus resultados de entrenamiento, evaluación y test.
Agregamos el código para entrenar el modelo:
# 8. Entrenamos al modelo
train_results = trainer.train()
trainer.save_model()
trainer.log_metrics("train", train_results.metrics)
trainer.save_metrics("train", train_results.metrics)
Respuesta esperada
***** train metrics *****
epoch = 4.0
train_loss = 0.3213
train_runtime = 0:01:57.42
train_samples_per_second = 124.948
train_steps_per_second = 15.636
{'eval_loss': 0.8058812022209167, 'eval_accuracy': 0.8455882352941176, 'eval_f1': 0.8868940754039497, 'eval_runtime': 1.7026, 'eval_samples_per_second': 239.639, 'eval_steps_per_second': 29.955, 'epoch': 3.27}
Finalmente evaluamos el modelo en el set de test
:
# 9. Evaluando en el conjunto de test
metrics = trainer.evaluate(clean_ds["test"])
trainer.log_metrics("eval", metrics)
trainer.save_metrics("eval", metrics)
Respuesta esperada:
***** eval metrics *****
epoch = 4.0
eval_accuracy = 0.793
eval_f1 = 0.8415
eval_loss = 0.6225
eval_runtime = 0:00:03.62
eval_samples_per_second = 476.381
eval_steps_per_second = 59.651
Felicidades, ya hemos terminado nuestro proyecto de NLP. Y hemos logrado obtener un accuracy
de casi 80% en el set de pruebas y un 84% En F1 score.
Nuestro modelo ya se encuentra en linea en el siguiente link: https://huggingface.co/platzi/platzi-distilroberta-base-mrpc-glue-gabriel-ichcanziho
El resumen de esta clase es muy sencillo, podemos crear un buen perfil en el HUB de Hugging Face para poder compartirlo como nuestro curriculum de machine learning y compartir con el público en general como han sido nuestros modelos, espacios, datasets entre otros.
El HUB nos permite observar la cantidad de descargas de nuestras contribuciones.
En realidad en los puntos anteriores se subía automáticamente al HUB, sin embargo esto es otra opción para agregar más información al momento de hacer PUSH al HUB.
kwargs = {
"finetuned_from": model.config._name_or_path,
"tasks": "text-classification",
"dataset": "datasetX",
"tags": ["text-classification"]
}
trainer.push_to_hub(commit_message="Lo logramos de nuevo equipo Platzi! 🤗", **kwargs)