Logotipo de Zephyrnet

CLIP multilingüe con Huggingface + PyTorch Lightning

Fecha:

CLIP multilingüe con Huggingface + PyTorch Lightning

Una descripción general de la formación de CLIP de OpenAI en Google Colab.


By Sachin Abeywardana, Ingeniero de aprendizaje automático en Canva

CLIP

Este es un tutorial de capacitación CLIP por OpenAI. CLIP fue diseñado para colocar tanto imágenes como texto en un nuevo espacio proyectado de modo que puedan mapearse entre sí simplemente mirando los productos punto.

Tradicionalmente, los conjuntos de entrenamiento como imagenet solo le permitían mapear imágenes a una sola clase (y por lo tanto a una palabra). Este método le permite asignar texto a imágenes, pero también se puede utilizar para asignar imágenes a texto si surge la necesidad.

Sin embargo, este blog en particular es específicamente cómo logramos entrenar esto en las GPU colab utilizando transformadores de cara abrazadora y relámpago pytorch.

Gracias a fastpages de fastai puedes ejecutar este blog en colab usando GPUS.
 

Aceptación

 
Felicitaciones al siguiente tutorial CLIP en la documentación de keras.

Lo importante a tener en cuenta acerca de las constantes es la tenue incrustación. Proyectaremos la salida de un resnet y transformadores en un espacio de 512 dimensiones.

EMBED_DIM = 512
TRANSFORMER_EMBED_DIM = 768
MAX_LEN = 128 # Maximum length of text
TEXT_MODEL = "distilbert-base-multilingual-cased" EPOCHS = 5
BATCH_SIZE = 64


Datos

 
Descargamos el conjunto de datos de coco que contiene 5 leyendas por imagen y tiene aproximadamente 82k imágenes. Tomamos el 20% de él como nuestro conjunto de validación.

Teniendo en cuenta que la columna vertebral de la imagen se entrena usando imagenet, la normalizamos usando las estadísticas de imagenet como se muestra en el paso de normalización de transformaciones. También cambiamos el tamaño de la imagen a 128 × 128 para asegurarnos de que se entrena en un tiempo razonable.

Advertencia: La descarga de los archivos tardará unos minutos (entre 5 y 10 minutos).

img = inv_tfm(img)
plt.imshow(np.rot90(img.transpose(0, 2), 3))
plt.title(target)
plt.show()


train_len = int(0.8*len(cap))
train_data, valid_data = random_split(cap, [train_len, len(cap) - train_len])
train_dl = DataLoader(train_data, BATCH_SIZE, pin_memory=True, shuffle=True, num_workers=4, drop_last=True)
valid_dl = DataLoader(valid_data, BATCH_SIZE, pin_memory=True, shuffle=False, num_workers=4, drop_last=False)


Modelo

 
Hay dos modelos principales, el VisionEncoder y del TextEncoder que tienen resnet18 y distilbert como columna vertebral. Para hacerlo multilingüe, simplemente elegimos el distilbert-multilingual modelo y eso es todo! No es necesario entrenar específicamente en palabras que no están en inglés, como pronto verá.

El  Projection módulo, toma las incrustaciones de codificadores de texto y visión y las proyecta en un espacio de 512 dimensiones.

Dos cosas a tener en cuenta:

  1. Hemos congelado la columna vertebral del codificador de texto y de visión y no reentrenamos sus pesos en absoluto.
  2. Para ambos codificadores, la salida final se normaliza para tener una longitud unitaria.
class Projection(nn.Module): def __init__(self, d_in: int, d_out: int, p: float=0.5) -> None: super().__init__() self.linear1 = nn.Linear(d_in, d_out, bias=False) self.linear2 = nn.Linear(d_out, d_out, bias=False) self.layer_norm = nn.LayerNorm(d_out) self.drop = nn.Dropout(p) def forward(self, x: torch.Tensor) -> torch.Tensor: embed1 = self.linear1(x) embed2 = self.drop(self.linear2(F.gelu(embed1))) embeds = self.layer_norm(embed1 + embed2) return embeds


class VisionEncoder(nn.Module): def __init__(self, d_out: int) -> None: super().__init__() base = models.resnet34(pretrained=True) d_in = base.fc.in_features base.fc = nn.Identity() self.base = base self.projection = Projection(d_in, d_out) for p in self.base.parameters(): p.requires_grad = False def forward(self, x): projected_vec = self.projection(self.base(x)) projection_len = torch.norm(projected_vec, dim=-1, keepdim=True) return projected_vec / projection_len


class TextEncoder(nn.Module): def __init__(self, d_out: int) -> None: super().__init__() self.base = AutoModel.from_pretrained(TEXT_MODEL) self.projection = Projection(TRANSFORMER_EMBED_DIM, d_out) for p in self.base.parameters(): p.requires_grad = False def forward(self, x): out = self.base(**x)[0] out = out[:, 0, :] # get CLS token output projected_vec = self.projection(out) projection_len = torch.norm(projected_vec, dim=-1, keepdim=True) return projected_vec / projection_len class Tokenizer: def __init__(self, tokenizer: BertTokenizer) -> None: self.tokenizer = tokenizer def __call__(self, x: str) -> AutoTokenizer: return self.tokenizer( x, max_length=MAX_LEN, truncation=True, padding=True, return_tensors="pt" )


Función de pérdida de CLIP

 
Para alguien como yo que no ha jugado con la pérdida contrastiva, esta fue la parte más interesante.

Sabemos que queremos que los vectores de la imagen correspondiente y el texto estén alineados. Lo que significa que el producto escalar tiene que estar lo más cerca posible de uno. Para todo lo demás, debemos empujarlo hacia 0.

Por lo tanto, para un título determinado, tomamos el softmax de los productos escalares en todas las imágenes y luego tomamos la pérdida de entropía cruzada. De manera similar, para una imagen determinada, repetimos el proceso en todos los subtítulos. Promediamos estas dos pérdidas.

En términos de qué elemento es el verdadero positivo dentro de un lote, recuerde que estamos enviando imágenes, pares de subtítulos ya alineados. Por lo tanto, queremos que todos los elementos diagonales se alineen mientras que todos los elementos fuera de la diagonal queremos empujar hacia cero.

def contrastive_loss(logits, dim): neg_ce = torch.diag(F.log_softmax(logits, dim=dim)) return -neg_ce.mean() def clip_loss(similarity: torch.Tensor) -> torch.Tensor: caption_loss = contrastive_loss(similarity, dim=0) image_loss = contrastive_loss(similarity, dim=1) return (caption_loss + image_loss) / 2.0 def metrics(similarity: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: y = torch.arange(len(similarity)).to(similarity.device) img2cap_match_idx = similarity.argmax(dim=1) cap2img_match_idx = similarity.argmax(dim=0) img_acc = (img2cap_match_idx == y).float().mean() cap_acc = (cap2img_match_idx == y).float().mean() return img_acc, cap_acc


Modelo

 
Si no ha usado Pytorch Lightning antes, el beneficio es que no necesita preocuparse por qué dispositivo colocarlo, recordando poner a cero el optimizador, etc. Todo eso está arreglado. Simplemente especifique los pasos de entrenamiento y validación, junto con el optimizador y estará listo para comenzar.

El otro beneficio que realmente me gusta es la tala. Solo necesitas escribir self.log("name", metric_to_track) y se registrará en tensorboard de forma predeterminada, o en cualquier otro tipo de registrador para el caso.

class Model(pl.LightningModule): def __init__(self, lr: float = 1e-3 ) -> None: super().__init__() self.vision_encoder = VisionEncoder(EMBED_DIM) self.caption_encoder = TextEncoder(EMBED_DIM) self.tokenizer = Tokenizer(AutoTokenizer.from_pretrained(TEXT_MODEL)) self.lr = lr def common_step(self, batch: Tuple[torch.Tensor, List[str]]) -> torch.Tensor: images, text = batch device = images.device text_dev = {k: v.to(device) for k, v in self.tokenizer(text).items()} image_embed = self.vision_encoder(images) caption_embed = self.caption_encoder(text_dev) similarity = caption_embed @ image_embed.T loss = clip_loss(similarity) img_acc, cap_acc = metrics(similarity) return loss, img_acc, cap_acc def training_step( self, batch: Tuple[torch.Tensor, List[str]], *args: list ) -> torch.Tensor: loss, img_acc, cap_acc = self.common_step(batch) self.log("training_loss", loss, on_step=True) self.log("training_img_acc", img_acc, on_step=True, prog_bar=True) self.log("training_cap_acc", cap_acc, on_step=True, prog_bar=True) return loss def validation_step( self, batch: Tuple[torch.Tensor, List[str]], *args: list ) -> torch.Tensor: loss, img_acc, cap_acc = self.common_step(batch) self.log("validation_loss", loss, on_step=True) self.log("validation_img_acc", img_acc, on_step=True, prog_bar=True) self.log("validation_cap_acc", cap_acc, on_step=True, prog_bar=True) return loss def configure_optimizers(self) -> torch.optim.Optimizer: vision_params = {"params": self.vision_encoder.projection.parameters(), "lr": self.lr} caption_params = {"params": self.caption_encoder.projection.parameters() , "lr": self.lr} return torch.optim.Adam([vision_params, caption_params])


Entrenar

 
El entrenamiento es sencillo, como se muestra en las cinco líneas siguientes. El uso de precisión de 16 bits redujo casi a la mitad el tiempo de entrenamiento de 16 minutos a 9 minutos por época. Observe lo fácil que fue agregar entrenamiento de precisión media y recorte de degradado.

También una cosa a tener en cuenta es que no pude hacer que esto funcione en TPU, así que si alguien sabe lo que necesito ajustar, hágamelo saber. Configuración tpu_cores=8 simplemente no funcionó.

model = Model(1e-3)
trainer = pl.Trainer( max_epochs= EPOCHS, gpus=torch.cuda.device_count(), gradient_clip_val=1.0, precision=16
)
trainer.fit(model, train_dl, valid_dl)


Downloading: "https://download.pytorch.org/models/resnet34-333f7ec4.pth" to /root/.cache/torch/hub/checkpoints/resnet34-333f7ec4.pth GPU available: True, used: True TPU available: None, using: 0 TPU cores Using native 16bit precision. | Name | Type | Params
--------------------------------------------------
0 | vision_encoder | VisionEncoder | 21.8 M
1 | caption_encoder | TextEncoder | 135 M --------------------------------------------------
1.2 M Trainable params
156 M Non-trainable params
157 M Total params
628.802 Total estimated model params size (MB) 1


Ejecute la siguiente celda si desea ver los registros en tensorboard. Pero aquí hay una captura de pantalla que tomé:tablero de tensor

%reload_ext tensorboard
%tensorboard --logdir ./lightning_logs/


Resultados

 
Compararé las inserciones de texto del primer lote (en el conjunto de validación) con todas las imágenes del conjunto de validación tomando el producto escalar entre ellas.

similarity = caption_embed @ image_embed.T
val, closest = similarity.topk(5, dim=-1)
similarity.shape


torch.Size([64, 16557])


draw_result(i, similarity_matrix) es una función de conveniencia que toma el título i-ésimo y la matriz de similitud, y traza las cinco imágenes más cercanas, junto con la imagen real. La similitud entre el título y la imagen se muestra en el título. Primero se imprime el título.

El histograma muestra la similitud del título con todas las imágenes como histograma.

draw_result(2, similarity)


A baseball player in the outfield with his hands up, standing next to a team mascot.


draw_result(1, similarity)


A watch and clock repair shop window with clocks on display.


draw_result(10, similarity)


A person on a skateboard on the ground.


A continuación se muestra la versión traducida de Google de uno de los subtítulos.

Título en inglés: "Una cebra de pie con la cabeza gacha y comiendo hierba en el suelo de tierra", traducido al español:

text = "Una cebra de pie con la cabeza gacha y comiendo hierba en el suelo de tierra."
text_dev = {k: v.to(device) for k, v in tokenizer(text).items()}
with torch.no_grad(): caption_embed_text = caption_encoder(text_dev) similarity_text = caption_embed_text @ image_embed.T


draw_result_single_query(10, similarity_text)


Skateboarder conducting a trick with bicycles in the background.


Nuevamente una versión traducida, esta vez al francés. Título en inglés: "Se muestra una computadora portátil en una pequeña plataforma de madera".

text = "Un ordinateur portable est affiché sur une petite plate-forme en bois."
text_dev = {k: v.to(device) for k, v in tokenizer(text).items()}
with torch.no_grad(): caption_embed_text = caption_encoder(text_dev) similarity_text = caption_embed_text @ image_embed.T draw_result_single_query(3, similarity_text)


Laptop computer on a small table on the side of a bed


Sin embargo, la traducción rusa a continuación está funcionando mal, por lo que claramente no es a prueba de balas. O quizás necesito entrenar un poco más. Título en inglés: “Una tienda llena de diferentes tipos de relojes.

text = "Магазин с разными часами"
text_dev = {k: v.to(device) for k, v in tokenizer(text).items()}
with torch.no_grad(): caption_embed_text = caption_encoder(text_dev) similarity_text = caption_embed_text @ image_embed.T draw_result_single_query(1, similarity_text)


A room filled with clocks through a window.


Y por último verifico una versión de una sola palabra. Observe cómo el perro parece un oso. ¿Quizás su nombre es oso?

text = "bear"
text_dev = {k: v.to(device) for k, v in tokenizer(text).items()}
with torch.no_grad(): caption_embed_text = caption_encoder(text_dev) similarity_text = caption_embed_text @ image_embed.T draw_result_single_query(1, similarity_text)


Large collection of digital and analog clocks on display.



Me encantaría escuchar cualquier pensamiento y comentario sobre lo anterior.

Autopromoción desvergonzada

 
Vea aquí mi curso en Machine Learning y Deep Learning (Use el código DEEPSCHOOL-MARCH para un 85% de descuento).

 
Bio: Sachin Abeywardana es ingeniero de aprendizaje automático en Canva. Hizo su doctorado en aprendizaje automático bayesiano, pero desde que se fue ha estado obsesionado con el maravilloso mundo del aprendizaje profundo. En estos días, está trabajando para llevar la bondad de la PNL a Canva.

Original. Publicado de nuevo con permiso.

Relacionado:

Coinsmart. Mejor Bitcoin-Börse en Europa
Fuente: https://www.kdnuggets.com/2021/03/multilingual-clip–huggingface-pytorch-lightning.html

punto_img

Información más reciente

punto_img