このコードが次の論文で使用され、引用されていることを知ってうれしいです。
Domino: Eyuboglu et。アル。 ICLR 2022で
GSCLIP: Zhu et。アル。 ICML 2022で
Semeval-2022のUIC-NLPタスク5: Cuervo et。アル。 Semeval-2022で
CDSBERT-ハーリーet。アル。デラウェア大学から(2023年9月)
Enigma-51: Ragusa et。アル。 (2023年11月)
このgithubレポの右側のセクションで引用情報を見つけることができます。名前:このリポジトリを引用するか、以下の引用情報を使用します。
@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 }
}2021年1月に、 Openaiが2つの新しいモデルを発表しました。Dall -EとClipの両方のマルチモダリティモデルと、何らかの方法でテキストと画像を接続するマルチモダリティモデルです。この記事では、 PytorchでClipモデルをゼロから実装します。 Openaiは、クリップモデルに関連するコードの一部をオープンソースしましたが、私はそれが威圧的であり、それは短くてシンプルなものとはほど遠いことがわかりました。また、Keras Code Examplesのクリップモデルに触発された優れたチュートリアルに出会いました。その一部をPytorchに翻訳して、このチュートリアルを最愛のPytorchで完全に構築しました。
Natural Language監督用紙から転送可能な視覚モデルを学習する際に、Openaiは、対照的な言語イメージの事前トレーニングのために、クリップと呼ばれる新しいモデルを導入します。一言で言えば、このモデルは、文章全体とそれが説明する画像の関係を学習します。モデルがトレーニングされたとき、入力文が与えられた場合、その文に対応する最も関連する画像を取得できるという意味です。ここで重要なことは、車、犬などの単一クラスの代わりに全文で訓練されていることです。直感は、文章全体で訓練された場合、モデルはより多くのことを学び、画像とテキストの間にパターンを見つけることができるということです。また、このモデルが画像の膨大なデータセットと対応するテキストでトレーニングされている場合、分類子としても機能する可能性があることを示しています。このエキサイティングなモデルとベンチマークデータセットに関する驚くべき結果について詳しく知るために、論文を勉強することをお勧めします。 1つだけに言及するために、この戦略でトレーニングされたクリップモデルは、分類の唯一のタスクのために最適化されたImagenet自体でトレーニングされたSOTAモデルよりもImagenetをよりよく分類します!
ティーザー(!)として、この記事でゼロから構築する最終モデルが何ができるか見てみましょう。

さらにいくつかの出力を見てみましょう:

# !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 config and cfgに関するメモ:Pythonスクリプトを使用してコードを作成し、Jupyterノートブックに変換しました。したがって、Pythonスクリプトの場合、構成は通常のPythonファイルであり、すべてのハイパーパラメーターを配置し、Jupyterノートブックの場合は、すべてのハイパーパラメーターを維持するためにノートブックの冒頭で定義されているクラスです。
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" ]この記事のタイトル画像でわかるように、両方の画像とそれらの説明テキストをエンコードする必要があります。そのため、データセットは画像とテキストの両方を返す必要があります。もちろん、テキストエンコーダーに生のテキストを送信するつもりはありません! HuggingfaceライブラリのDistilbertモデル(Bertよりも小さいが、Bertとほぼ同様にBertを実行します)をテキストエンコーダーとして使用します。したがって、Distilbertトークネイザーを使用して文(キャプション)をトークン化し、トークンID(input_ids)と注意マスクにディスティルバートに供給する必要があります。したがって、データセットはトークン化にも注意を払う必要があります。以下に、データセットのコードを見ることができます。その下では、コードで起こっている最も重要なことを説明します。
__init__では、実際にはハグのあるtokinzerであるトークン剤オブジェクトを受け取ります。このトークネザーは、モデルを実行するときにロードされます。指定されたmax_lengthにキャプションをパディングして切り捨てています。 __getitem__では、最初にキー入力_idsとattention_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 Library(TIMM)を使用しています。これは、ResnetからEfficientYNetsなどにさまざまな画像モデルを利用できるようにしています。ここでは、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をテキストエンコーダーとして使用します。 Bigger Brother Bertのように、2つの特別なトークンが実際の入力トークンに追加されます。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 , :]Keras Codeの例を使用して、投影ヘッドの実装を使用して、Pytorchで以下を書き込みました。画像とテキストの両方を固定サイズのベクトル(画像の場合は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)。その後、損失を計算します。繰り返しになりますが、クリップペーパーを読むことをお勧めしますが、この部分を説明するために最善を尽くします。
線形代数では、2つのベクトルが同様の特性(互いに似ている)であるかどうかを測定する1つの一般的な方法は、ドット積を計算することです(一致するエントリを掛けてそれらの合計を取る)。最終的な数字が大きい場合、それらは似ています。
わかった!私が言ったことは、この損失関数を理解するために念頭に置く最も重要なことです。続けましょう。 2つのベクトルについて話しましたが、ここには何がありますか? Image_embeddings、形状のあるマトリックス(batch_size、256)、および形状のtext_embeddings(batch_size、256)があります。簡単!つまり、2つの単一ベクトルの代わりに2つのグループのベクトルがあることを意味します。ベクトルの2つのグループ(2つのマトリックス)の2つのグループが互いにどれほど類似しているかをどのように測定しますか?繰り返しますが、DOT製品(Pytorchの@オペレーターは、この場合にDOT製品またはマトリックスの乗算を行います)。これら2つのマトリックスを掛けることができるように、2番目のマトリックスを転置します。さて、ロジットを呼び出す形状(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 ))したがって、ロジットは、最良の場合には、ソフトマックスを取ると斜めに1.0を持つマトリックスになります(派手な単語でそれを呼ぶアイデンティティマトリックス!)。損失関数のジョブは、モデルの予測をターゲットと同様にすることであるため(少なくともほとんどの場合!)、ターゲットのようなマトリックスが必要です。それが、上記のコードブロックでImages_similarityとtexts_similarity Matricesを計算している理由です。
ターゲットマトリックスを手に入れたので、単純なクロスエントロピーを使用して実際の損失を計算します。コードブロックの下部に表示できる関数として、クロスエントロピーの完全なマトリックス形式を書きました。わかった!終わった!簡単ではありませんでしたか?!さて、次の段落を無視することができますが、興味がある場合は重要なメモがあります。
より単純なアプローチを使用しなかった理由は次のとおりです。Pytorchでこの損失を計算する簡単な方法があることを認める必要があります。これを行うことで:nn.crossentropyloss()(logits、torch.arange(batch_size))。なぜここで使用しなかったのですか? 2つの理由で。 1-使用しているデータセットには、単一の画像の複数のキャプションがあります。したがって、同様のキャプションを持つ2つの同一の画像がバッチに存在する可能性があります(まれですが、発生する可能性があります)。この簡単な方法で損失をとると、この可能性は無視され、モデルは実際に同じ2つの表現(異なると仮定)を引き離すことを学びます。明らかに、これが起こりたくないので、これらのエッジケースを処理する方法でターゲットマトリックス全体を計算しました。 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これがモデルをトレーニングするための便利な機能です。ここではあまり起こっていません。バッチをロードし、モデルに供給し、オプティマイザーと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つの時代でも十分です!)。トレーニングが開始されるまでに1分かかる場合があります。電車で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_Embedings、およびテキストクエリを取得します。検証セットから最も関連性の高い画像が表示されます!驚くべきことではありませんか?結局、それがどのように機能するか見てみましょう!
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 ()これがこの機能の使用方法です。 aaaannnnddddd結果:
find_matches ( model ,
image_embeddings ,
query = "a group of people dancing in a party" ,
image_filenames = valid_df [ 'image' ]. values ,
n = 9 )
この記事を楽しんだことを願っています。この論文を実装することは、私にとって本当に興味深い経験でした。彼が提供した偉大なケラスのコードの例について、ハリド・サラマに感謝したいと思います。