이 코드가 다음 논문에서 사용 및 인용되었음을 알게되어 기쁩니다.
도미노 : Eyuboglu et. 의 교차 모달 삽입으로 체계적인 오류 발견. 알. ICLR 2022 에서
GSCLIP : Zhu et. 알. ICML 2022 에서
SEMEVAL-2022의 UIC-NLP 작업 5 : Cuervo et. 알. Semeval-2022 에서
CDSBERT- Hallee et. 의 코돈 인식으로 단백질 언어 모델을 확장합니다. 알. 델라웨어 대학교 (2023 년 9 월)
Enigma-51 : Ragusa et. 알. (2023 년 11 월)
이 GitHub Repo 페이지의 오른쪽 섹션에서 인용 정보를 찾을 수 있습니다.이 저장소를 인용하거나 아래 인용 정보를 사용하십시오.
@software { Shariatnia_Simple_CLIP_2021 ,
author = { Shariatnia, M. Moein } ,
doi = { 10.5281/zenodo.6845731 } ,
month = { 4 } ,
title = { {Simple CLIP} } ,
version = { 1.0.0 } ,
year = { 2021 }
}OpenAi는 2021 년 1 월에 Dall-E 와 Clip의 두 가지 새로운 모델을 발표 한 것은 텍스트와 이미지를 어떤 식 으로든 연결하는 다중 모드 모델을 모두 발표했습니다. 이 기사에서는 Pytorch 에서 처음부터 클립 모델을 구현할 것입니다. OpenAi는 클립 모델과 관련된 일부 코드를 오픈 소싱했지만 협박을 발견했으며 짧고 간단한 것과는 거리가 멀었습니다. 또한 Keras Code 예제의 Clip Model에서 영감을 얻은 좋은 튜토리얼을 발견했으며 일부 일부를 Pytorch로 번역하여 사랑하는 Pytorch와 함께이 튜토리얼을 완전히 구축했습니다!
자연어 감독 용지에서 전송 가능한 시각적 모델을 학습 할 때 OpenAI는 대조적 인 언어 이미지 사전 훈련을 위해 클립 이라고하는 새로운 모델을 소개합니다. 간단히 말해서,이 모델은 전체 문장과 설명하는 이미지 사이의 관계를 배웁니다. 모델이 훈련 될 때 입력 문장이 주어지면 해당 문장에 해당하는 가장 관련된 이미지를 검색 할 수 있습니다. 여기서 중요한 것은 자동차, 개 등과 같은 단일 클래스 대신 전체 문장으로 훈련된다는 것입니다. 직관은 전체 문장에 대해 훈련 할 때 모델이 더 많은 것을 배우고 이미지와 텍스트 사이의 패턴을 찾을 수 있다는 것입니다. 또한이 모델이 이미지의 거대한 데이터 세트와 해당 텍스트에 대한 교육을 받으면 분류기 역할을 할 수도 있음을 보여줍니다. 이 흥미 진진한 모델과 벤치마킹 데이터 세트에 대한 놀라운 결과에 대해 더 많이 배우기 위해 논문을 연구하는 것이 좋습니다. 하나만 말하면,이 전략으로 훈련 된 클립 모델은 ImageNet 자체에서 훈련 된 SOTA 모델보다 분류하는 유일한 작업을 위해 최적화 된 SOTA 모델보다 Imagenet을 더 잘 분류합니다!
Teas (!) 로서이 기사에서 처음부터 구축 할 최종 모델은 다음과 같습니다. "Skateboard로 점프하는 소년"또는 "Swing에서 점프하는 소녀"와 같은 쿼리 (Raw Text)가 주어지면 모델은 가장 관련성이 높은 이미지를 검색합니다.

더 많은 출력을 보자.

# !pip install timm
# !pip install transformers import os
import cv2
import gc
import numpy as np
import pandas as pd
import itertools
from tqdm . autonotebook import tqdm
import albumentations as A
import torch
from torch import nn
import torch . nn . functional as F
import timm
from transformers import DistilBertModel , DistilBertConfig , DistilBertTokenizer 구성 및 CFG에 대한 메모 : Python 스크립트가있는 코드를 작성한 다음 Jupyter 노트북으로 변환했습니다. 따라서 Python 스크립트의 경우 Config는 모든 초 파라미터와 Jupyter 노트북의 경우 노트북의 시작 부분에 정의 된 클래스를 모든 과복 미터를 유지하기 위해 일반적인 Python 파일입니다.
class CFG :
debug = False
image_path = "C:/Moein/AI/Datasets/Flicker-8k/Images"
captions_path = "C:/Moein/AI/Datasets/Flicker-8k"
batch_size = 32
num_workers = 4
head_lr = 1e-3
image_encoder_lr = 1e-4
text_encoder_lr = 1e-5
weight_decay = 1e-3
patience = 1
factor = 0.8
epochs = 4
device = torch . device ( "cuda" if torch . cuda . is_available () else "cpu" )
model_name = 'resnet50'
image_embedding = 2048
text_encoder_model = "distilbert-base-uncased"
text_embedding = 768
text_tokenizer = "distilbert-base-uncased"
max_length = 200
pretrained = True # for both image encoder and text encoder
trainable = True # for both image encoder and text encoder
temperature = 1.0
# image size
size = 224
# for projection head; used for both image and text encoders
num_projection_layers = 1
projection_dim = 256
dropout = 0.1 class AvgMeter :
def __init__ ( self , name = "Metric" ):
self . name = name
self . reset ()
def reset ( self ):
self . avg , self . sum , self . count = [ 0 ] * 3
def update ( self , val , count = 1 ):
self . count += count
self . sum += val * count
self . avg = self . sum / self . count
def __repr__ ( self ):
text = f" { self . name } : { self . avg :.4f } "
return text
def get_lr ( optimizer ):
for param_group in optimizer . param_groups :
return param_group [ "lr" ]이 기사의 Tittle 이미지에서 볼 수 있듯이 이미지와 설명 텍스트를 모두 인코딩해야합니다. 따라서 데이터 세트는 이미지와 텍스트를 모두 반환 해야합니다. 물론 우리는 텍스트 인코더에 원시 텍스트를 공급하지 않을 것입니다! 우리는 Distilbert 모델 (Bert보다 작지만 Bert 만큼 작용)을 텍스트 인코더로 사용합니다. 따라서 문장 (캡션)을 Distilbert Tokenizer로 토큰 화 한 다음 토큰 ID (input_ids)와주의 마스크를 Distilbert에 공급해야합니다. 따라서 데이터 세트는 토큰 화도 처리해야합니다. 아래는 데이터 세트의 코드를 볼 수 있습니다. 아래는 코드에서 일어나는 가장 중요한 일을 설명하겠습니다.
__init__ 에서 우리는 실제로 huggingface tokinzer입니다. 이 토큰 화기는 모델을 실행할 때로드됩니다. 우리는 캡션을 지정된 max_length로 패딩하고 잘립니다. __getItem__ 에서는 먼저 Keys Input_IDS 및 Interection_Mask가있는 사전 인 인코딩 된 캡션을로드하고 값을 벗어나 텐서를 만들고 해당 이미지를로드하고 (있는 경우) 텐서로 만들고 "이미지"와 함께 사전에 넣습니다. 마지막으로 캡션의 원시 텍스트를 시각화 목적으로 만 사전에 "캡션"의 키 "캡션"과 함께 넣습니다.
추가 데이터 증강을 사용하지는 않았지만 모델의 성능을 향상시키려는 경우 추가 할 수 있습니다.
class CLIPDataset ( torch . utils . data . Dataset ):
def __init__ ( self , image_filenames , captions , tokenizer , transforms ):
"""
image_filenames and cpations must have the same length; so, if there are
multiple captions for each image, the image_filenames must have repetitive
file names
"""
self . image_filenames = image_filenames
self . captions = list ( captions )
self . encoded_captions = tokenizer (
list ( captions ), padding = True , truncation = True , max_length = CFG . max_length
)
self . transforms = transforms
def __getitem__ ( self , idx ):
item = {
key : torch . tensor ( values [ idx ])
for key , values in self . encoded_captions . items ()
}
image = cv2 . imread ( f" { CFG . image_path } / { self . image_filenames [ idx ] } " )
image = cv2 . cvtColor ( image , cv2 . COLOR_BGR2RGB )
image = self . transforms ( image = image )[ 'image' ]
item [ 'image' ] = torch . tensor ( image ). permute ( 2 , 0 , 1 ). float ()
item [ 'caption' ] = self . captions [ idx ]
return item
def __len__ ( self ):
return len ( self . captions )
def get_transforms ( mode = "train" ):
if mode == "train" :
return A . Compose (
[
A . Resize ( CFG . size , CFG . size , always_apply = True ),
A . Normalize ( max_pixel_value = 255.0 , always_apply = True ),
]
)
else :
return A . Compose (
[
A . Resize ( CFG . size , CFG . size , always_apply = True ),
A . Normalize ( max_pixel_value = 255.0 , always_apply = True ),
]
)이미지 인코더 코드는 간단합니다. 여기에서 Pytorch Image Models 라이브러리 (Timm)를 사용하여 리스넷에서 효율적으로 다양한 이미지 모델을 사용할 수 있습니다. 여기서는 resnet50을 이미지 인코더로 사용합니다. 새 라이브러리를 설치하지 않으려면 Torchvision 라이브러리를 사용하여 Resnet을 쉽게 사용할 수 있습니다.
코드는 모델의 출력 채널 크기로 각 이미지를 고정 크기 벡터로 인코딩합니다 (Resnet50의 경우 벡터 크기는 2048 ). 이것은 nn.adaptiveavgpool2d () 레이어 이후의 출력입니다.
class ImageEncoder ( nn . Module ):
"""
Encode images to a fixed size vector
"""
def __init__ (
self , model_name = CFG . model_name , pretrained = CFG . pretrained , trainable = CFG . trainable
):
super (). __init__ ()
self . model = timm . create_model (
model_name , pretrained , num_classes = 0 , global_pool = "avg"
)
for p in self . model . parameters ():
p . requires_grad = trainable
def forward ( self , x ):
return self . model ( x )앞에서 언급했듯이 Distilbert를 텍스트 인코더로 사용하겠습니다. 더 큰 형제 Bert와 마찬가지로, 두 개의 특수 토큰이 실제 입력 토큰에 추가됩니다 : CLS 및 SEP는 문장의 시작과 끝을 표시합니다. 문장의 전체 표현을 잡으려면 (관련 Bert 및 Distilbert 논문에서 지적한 것처럼) CLS 토큰의 최종 표현을 사용 하고이 표현이 문장의 전반적인 의미 (캡션)를 포착하기를 바랍니다. 이런 식으로 생각하면, 우리가 이미지에 한 일과 비슷하고 고정 크기 벡터로 변환했습니다.
Distilbert (및 Bert)의 경우 각 토큰의 출력 숨겨진 표현은 크기 768을 가진 벡터입니다. 따라서 전체 캡션은 크기가 768 인 CLS 토큰 표현으로 인코딩됩니다.
class TextEncoder ( nn . Module ):
def __init__ ( self , model_name = CFG . text_encoder_model , pretrained = CFG . pretrained , trainable = CFG . trainable ):
super (). __init__ ()
if pretrained :
self . model = DistilBertModel . from_pretrained ( model_name )
else :
self . model = DistilBertModel ( config = DistilBertConfig ())
for p in self . model . parameters ():
p . requires_grad = trainable
# we are using the CLS token hidden representation as the sentence's embedding
self . target_token_idx = 0
def forward ( self , input_ids , attention_mask ):
output = self . model ( input_ids = input_ids , attention_mask = attention_mask )
last_hidden_state = output . last_hidden_state
return last_hidden_state [:, self . target_token_idx , :]Pytorch에 다음을 작성하기 위해 Projection Head의 Keras 코드 예제 구현을 사용했습니다. 이제 우리는 이미지와 텍스트를 고정 크기 벡터 (이미지의 경우 2048, 텍스트의 경우 768)로 인코딩 했으므로 이미지와 텍스트를 비교하고 비교적 이미지와 텍스트를 분리하고 그 경기를 함께 밀기 위해서는 이미지와 텍스트에 대해 비슷한 차원 으로 새로운 세계 (!)로 가져와야합니다. 따라서 다음 코드는 2048 및 768 치수 벡터를 256 (Projection_dim) 차원 세계로 가져 오며,이를 비교할 수 있습니다.
"embedding_dim"은 입력 벡터의 크기 (이미지의 경우 2048, 텍스트의 경우 768)이며 "projection_dim"은 출력 벡터의 크기이며,이 경우 256입니다. 이 부분의 세부 사항을 이해하려면 클립 용지를 참조 할 수 있습니다.
class ProjectionHead ( nn . Module ):
def __init__ (
self ,
embedding_dim ,
projection_dim = CFG . projection_dim ,
dropout = CFG . dropout
):
super (). __init__ ()
self . projection = nn . Linear ( embedding_dim , projection_dim )
self . gelu = nn . GELU ()
self . fc = nn . Linear ( projection_dim , projection_dim )
self . dropout = nn . Dropout ( dropout )
self . layer_norm = nn . LayerNorm ( projection_dim )
def forward ( self , x ):
projected = self . projection ( x )
x = self . gelu ( projected )
x = self . fc ( x )
x = self . dropout ( x )
x = x + projected
x = self . layer_norm ( x )
return x 이 부분은 모든 재미가 일어나는 곳입니다! 여기서 손실 기능에 대해서도 이야기하겠습니다. Keras 코드 예제의 일부 코드를 Pytorch로 번역 하여이 부분을 작성했습니다. 코드를 보고이 코드 블록 아래의 설명을 읽으십시오.
여기서는 기본 모델을 구현하기 위해 구축 한 이전 모듈을 사용합니다. __init__ 함수는 자명합니다. 전방 기능에서는 먼저 이미지와 텍스트를 고정 크기 벡터 (다른 차원)로 별도로 인코딩합니다. 그 후, 별도의 프로젝션 모듈을 사용하여 이전에 이야기했던 공유 세계 (공간)에 투사합니다. 여기서 인코딩은 비슷한 모양이됩니다 (우리의 경우 256). 그 후 우리는 손실을 계산할 것입니다. 다시 한 번 클립 종이를 읽는 것이 좋습니다. 그러나 더 나아질 것입니다. 그러나이 부분을 설명하기 위해 최선을 다하겠습니다.
선형 대수 에서, 두 벡터가 비슷한 특성인지 (서로와 비슷한 지)를 측정하는 한 가지 일반적인 방법은 DOT 제품을 계산하는 것입니다 (일치하는 항목을 곱하고 합계를 가져옵니다). 마지막 숫자가 크면 비슷하며 작다면 (비교적 말하면) 아닙니다!
좋아요! 내가 방금 말한 것은이 손실 기능을 이해하기 위해 가장 중요한 것입니다. 계속합시다. 우리는 두 벡터에 대해 이야기했지만 여기에 무엇이 있습니까? image_embeddings, shape (batch_size, 256)가있는 행렬, Shape (batch_size, 256)의 Text_embeddings가 있습니다. 충분히! 그것은 우리가 두 개의 단일 벡터 대신 두 그룹의 벡터를 가지고 있음을 의미합니다. 우리는 두 그룹의 벡터 (두 행렬)가 서로 얼마나 비슷한지를 측정합니까? 다시 말하지만, Dot Product (Pytorch의@ 연산자는이 경우 Dot Product 또는 Matrix 곱셈을 수행합니다). 이 두 행렬을 함께 곱할 수 있도록 두 번째 매트릭스를 전환합니다. 좋아, 우리는 로트라고 부르는 모양 (batch_size, batch_size)의 행렬을 얻습니다. (우리의 경우 온도는 1.0과 같으므로 차이가 없습니다. 당신은 그것을 가지고 놀면서 어떤 차이가 일어나는지 볼 수 있습니다. 또한 논문을보고 그것이 왜 여기에 있는지 확인하십시오!)
나는 당신이 여전히 나와 함께 있기를 바랍니다! 괜찮지 않다면 코드를 검토하고 모양을 확인하십시오. 이제 로그가 생겼으므로 목표가 필요합니다. 목표를 얻는 더 간단한 방법이 있다고 말해야하지만 우리의 경우에 이것을해야했습니다 (다음 단락에서 이유에 대해 이야기하겠습니다).
이 모델이 배우기를 바라는 것을 고려해 봅시다. 우리는 주어진 이미지와 설명하는 캡션에 대해 "유사한 표현 (벡터)"을 배우기를 원합니다. 우리는 이미지를 제공하거나 설명하는 텍스트를 제공한다는 것을 의미합니다. 두 가지 모두에 대해 동일한 256 개의 크기의 벡터를 생산하기를 원합니다.
class CLIPModel ( nn . Module ):
def __init__ (
self ,
temperature = CFG . temperature ,
image_embedding = CFG . image_embedding ,
text_embedding = CFG . text_embedding ,
):
super (). __init__ ()
self . image_encoder = ImageEncoder ()
self . text_encoder = TextEncoder ()
self . image_projection = ProjectionHead ( embedding_dim = image_embedding )
self . text_projection = ProjectionHead ( embedding_dim = text_embedding )
self . temperature = temperature
def forward ( self , batch ):
# Getting Image and Text Features
image_features = self . image_encoder ( batch [ "image" ])
text_features = self . text_encoder (
input_ids = batch [ "input_ids" ], attention_mask = batch [ "attention_mask" ]
)
# Getting Image and Text Embeddings (with same dimension)
image_embeddings = self . image_projection ( image_features )
text_embeddings = self . text_projection ( text_features )
# Calculating the Loss
logits = ( text_embeddings @ image_embeddings . T ) / self . temperature
images_similarity = image_embeddings @ image_embeddings . T
texts_similarity = text_embeddings @ text_embeddings . T
targets = F . softmax (
( images_similarity + texts_similarity ) / 2 * self . temperature , dim = - 1
)
texts_loss = cross_entropy ( logits , targets , reduction = 'none' )
images_loss = cross_entropy ( logits . T , targets . T , reduction = 'none' )
loss = ( images_loss + texts_loss ) / 2.0 # shape: (batch_size)
return loss . mean ()
def cross_entropy ( preds , targets , reduction = 'none' ):
log_softmax = nn . LogSoftmax ( dim = - 1 )
loss = ( - targets * log_softmax ( preds )). sum ( 1 )
if reduction == "none" :
return loss
elif reduction == "mean" :
return loss . mean ()따라서 최상의 시나리오에서 Text_embeddings 및 Image_embedding Matricies는 비슷한 것을 묘사하기 때문에 동일해야합니다. 지금 생각합시다 : 이런 일이 발생하면 로그 매트릭스는 어떤 모습일까요? 간단한 예로 보자!
# A simple Example
batch_size = 4
dim = 256
embeddings = torch . randn ( batch_size , dim )
out = embeddings @ embeddings . T
print ( F . softmax ( out , dim = - 1 ))따라서 가장 좋은 경우 로이트는 우리가 SoftMax를 복용하면 대각선에 1.0을 가질 수있는 행렬이 될 것입니다 (멋진 단어로 호출하기위한 ID 매트릭스). 손실 함수의 임무는 모델의 예측을 대상과 유사하게 만드는 것이므로 (적어도 대부분의 경우!) 대상과 같은 매트릭스를 원합니다. 이것이 우리가 위의 코드 블록에서 이미지를 계산하는 이유입니다.
우리는 Targets Matrix를 얻었으므로 간단한 크로스 엔트로피를 사용하여 실제 손실을 계산합니다. 크로스 엔트로피의 전체 매트릭스 형태를 코드 블록 하단에서 볼 수있는 함수로 작성했습니다. 좋아요! 우리는 끝났다! 간단하지 않았습니까?! 좋아요, 다음 단락을 무시할 수 있지만 궁금하다면 중요한 메모가 있습니다.
더 간단한 접근 방식을 사용하지 않은 이유는 다음과 같습니다 . Pytorch 에서이 손실을 계산하는 더 간단한 방법이 있음을 인정해야합니다. 이렇게함으로써 : nn.crossentropyloss () (logits, torch.arange (batch_size)). 왜 여기서 사용하지 않았습니까? 두 가지 이유로. 1- 우리가 사용중인 데이터 세트에는 단일 이미지에 대한 여러 캡션이 있습니다. 따라서 비슷한 캡션을 가진 두 개의 동일한 이미지가 배치에 존재할 가능성이 있습니다 (드물지만 발생할 수 있습니다). 이 쉬운 방법으로 손실을 입으면이 가능성이 무시하고 모델은 실제로 동일한 두 가지 표현을 분리하는 법을 배웁니다. 분명히, 우리는이 일이 일어나기를 원하지 않으므로 이러한 모서리 케이스를 처리하는 방식으로 전체 대상 매트릭스를 계산했습니다. 2- 내가 한 방식으로 그렇게 하면서이 손실 기능에서 무슨 일이 일어나고 있는지 더 잘 이해해주었습니다. 그래서 나는 그것이 당신에게도 더 나은 직관을 줄 것이라고 생각했습니다!
다음은 열차 및 유효한 데이터 로더, 모델을로드 한 다음 모델을 교육하고 평가하는 데 도움이되는 몇 가지 기능입니다. 여기에는 많은 일이 일어나지 않습니다. 간단한 교육 루프 및 유틸리티 기능 만 있습니다
def make_train_valid_dfs ():
dataframe = pd . read_csv ( f" { CFG . captions_path } /captions.csv" )
max_id = dataframe [ "id" ]. max () + 1 if not CFG . debug else 100
image_ids = np . arange ( 0 , max_id )
np . random . seed ( 42 )
valid_ids = np . random . choice (
image_ids , size = int ( 0.2 * len ( image_ids )), replace = False
)
train_ids = [ id_ for id_ in image_ids if id_ not in valid_ids ]
train_dataframe = dataframe [ dataframe [ "id" ]. isin ( train_ids )]. reset_index ( drop = True )
valid_dataframe = dataframe [ dataframe [ "id" ]. isin ( valid_ids )]. reset_index ( drop = True )
return train_dataframe , valid_dataframe
def build_loaders ( dataframe , tokenizer , mode ):
transforms = get_transforms ( mode = mode )
dataset = CLIPDataset (
dataframe [ "image" ]. values ,
dataframe [ "caption" ]. values ,
tokenizer = tokenizer ,
transforms = transforms ,
)
dataloader = torch . utils . data . DataLoader (
dataset ,
batch_size = CFG . batch_size ,
num_workers = CFG . num_workers ,
shuffle = True if mode == "train" else False ,
)
return dataloader다음은 모델을 훈련시키는 편리한 기능입니다. 여기에는 많은 일이 일어나지 않습니다. 배치를로드하고 모델에 공급하고 Optimizer 및 LR_Scheduler를 밟습니다.
def train_epoch ( model , train_loader , optimizer , lr_scheduler , step ):
loss_meter = AvgMeter ()
tqdm_object = tqdm ( train_loader , total = len ( train_loader ))
for batch in tqdm_object :
batch = { k : v . to ( CFG . device ) for k , v in batch . items () if k != "caption" }
loss = model ( batch )
optimizer . zero_grad ()
loss . backward ()
optimizer . step ()
if step == "batch" :
lr_scheduler . step ()
count = batch [ "image" ]. size ( 0 )
loss_meter . update ( loss . item (), count )
tqdm_object . set_postfix ( train_loss = loss_meter . avg , lr = get_lr ( optimizer ))
return loss_meter
def valid_epoch ( model , valid_loader ):
loss_meter = AvgMeter ()
tqdm_object = tqdm ( valid_loader , total = len ( valid_loader ))
for batch in tqdm_object :
batch = { k : v . to ( CFG . device ) for k , v in batch . items () if k != "caption" }
loss = model ( batch )
count = batch [ "image" ]. size ( 0 )
loss_meter . update ( loss . item (), count )
tqdm_object . set_postfix ( valid_loss = loss_meter . avg )
return loss_meter
def main ():
train_df , valid_df = make_train_valid_dfs ()
tokenizer = DistilBertTokenizer . from_pretrained ( CFG . text_tokenizer )
train_loader = build_loaders ( train_df , tokenizer , mode = "train" )
valid_loader = build_loaders ( valid_df , tokenizer , mode = "valid" )
model = CLIPModel (). to ( CFG . device )
params = [
{ "params" : model . image_encoder . parameters (), "lr" : CFG . image_encoder_lr },
{ "params" : model . text_encoder . parameters (), "lr" : CFG . text_encoder_lr },
{ "params" : itertools . chain (
model . image_projection . parameters (), model . text_projection . parameters ()
), "lr" : CFG . head_lr , "weight_decay" : CFG . weight_decay }
]
optimizer = torch . optim . AdamW ( params , weight_decay = 0. )
lr_scheduler = torch . optim . lr_scheduler . ReduceLROnPlateau (
optimizer , mode = "min" , patience = CFG . patience , factor = CFG . factor
)
step = "epoch"
best_loss = float ( 'inf' )
for epoch in range ( CFG . epochs ):
print ( f"Epoch: { epoch + 1 } " )
model . train ()
train_loss = train_epoch ( model , train_loader , optimizer , lr_scheduler , step )
model . eval ()
with torch . no_grad ():
valid_loss = valid_epoch ( model , valid_loader )
if valid_loss . avg < best_loss :
best_loss = valid_loss . avg
torch . save ( model . state_dict (), "best.pt" )
print ( "Saved Best Model!" )
lr_scheduler . step ( valid_loss . avg )다음 셀을 실행하여 모델을 시작합니다. 커널을 GPU 모드에 넣으십시오. 모든 시대는 GPU에서 약 24 분이 걸립니다 (하나의 시대도 충분합니다!). 훈련이 시작되기까지 1 분이 걸릴 수 있습니다. 기차와 유효한 데이터 세트에서 한 번 모든 캡션을 인코딩하므로 중지하지 마십시오! 모든 것이 잘 작동합니다.
main ()좋아요! 우리는 모델을 훈련 시켰습니다. 이제 우리는 우리의 경우 모델에 텍스트를 제공하고 보이지 않는 유효성 검사 (또는 테스트) 세트에서 가장 관련성이 높은 이미지를 검색 할 추론을해야합니다.
이 기능에서는 훈련 후 저장 한 모델을로드하고 유효성 검사 세트에 이미지를 공급하고 image_embeddings를 모양 (valid_set_size, 256) 및 모델 자체로 반환합니다.
def get_image_embeddings ( valid_df , model_path ):
tokenizer = DistilBertTokenizer . from_pretrained ( CFG . text_tokenizer )
valid_loader = build_loaders ( valid_df , tokenizer , mode = "valid" )
model = CLIPModel (). to ( CFG . device )
model . load_state_dict ( torch . load ( model_path , map_location = CFG . device ))
model . eval ()
valid_image_embeddings = []
with torch . no_grad ():
for batch in tqdm ( valid_loader ):
image_features = model . image_encoder ( batch [ "image" ]. to ( CFG . device ))
image_embeddings = model . image_projection ( image_features )
valid_image_embeddings . append ( image_embeddings )
return model , torch . cat ( valid_image_embeddings ) _ , valid_df = make_train_valid_dfs ()
model , image_embeddings = get_image_embeddings ( valid_df , "best.pt" )이 기능은 모델을 원했던 최종 작업을 수행합니다. 모델, Image_embeddings 및 텍스트 쿼리를 가져옵니다. 유효성 검사 세트에서 가장 관련성이 높은 이미지를 표시합니다! 놀랍지 않나요? 결국 그것이 어떻게 작동하는지 봅시다!
def find_matches ( model , image_embeddings , query , image_filenames , n = 9 ):
tokenizer = DistilBertTokenizer . from_pretrained ( CFG . text_tokenizer )
encoded_query = tokenizer ([ query ])
batch = {
key : torch . tensor ( values ). to ( CFG . device )
for key , values in encoded_query . items ()
}
with torch . no_grad ():
text_features = model . text_encoder (
input_ids = batch [ "input_ids" ], attention_mask = batch [ "attention_mask" ]
)
text_embeddings = model . text_projection ( text_features )
image_embeddings_n = F . normalize ( image_embeddings , p = 2 , dim = - 1 )
text_embeddings_n = F . normalize ( text_embeddings , p = 2 , dim = - 1 )
dot_similarity = text_embeddings_n @ image_embeddings_n . T
values , indices = torch . topk ( dot_similarity . squeeze ( 0 ), n * 5 )
matches = [ image_filenames [ idx ] for idx in indices [:: 5 ]]
_ , axes = plt . subplots ( 3 , 3 , figsize = ( 10 , 10 ))
for match , ax in zip ( matches , axes . flatten ()):
image = cv2 . imread ( f" { CFG . image_path } / { match } " )
image = cv2 . cvtColor ( image , cv2 . COLOR_BGR2RGB )
ax . imshow ( image )
ax . axis ( "off" )
plt . show ()이것이 우리 가이 기능을 사용하는 방법입니다. aaaannnndddd 결과 :
find_matches ( model ,
image_embeddings ,
query = "a group of people dancing in a party" ,
image_filenames = valid_df [ 'image' ]. values ,
n = 9 )
이 기사를 즐겼기를 바랍니다. 이 논문을 구현하는 것은 저에게 정말 흥미로운 경험이었습니다. 나는 Pytorch에서 비슷한 것을 쓰도록 영감을 준 Great Keras 코드 예제에 대해 Khalid Salama에게 감사하고 싶습니다.