Files
torzdf 837bc2d51d Faceswap 3 (#1516)
* FaceSwap 3 (#1515)
* Update extract pipeline
* Update requirements + setup for nvidia
* Remove allow-growth option
* tf.keras to keras updates
* lib.model.losses - Port + fix all loss functions for Keras3
* lib.model - port initializers, layers. normalization to Keras3
* lib.model.autoclip to Keras 3
* Update mixed precision layer storage
* model file to .keras format
* Restructure nn_blocks to initialize layers in __init__
* Tensorboard
  - Trainer: Add Torch compatible Tensorboard callbacks
  - GUI event reader remove TF dependency
* Loss logging
  - Flush TB logs on save
  - Replace TB live iterator for GUI
* Backup models on total loss drop rather than per side
* Update all models to Keras3 Compat
* Remove lib.model.session
* Update clip ViT to Keras 3
* plugins.extract.mask.unet-dfl - Fix for Keras3/Torch backend
* Port AdaBelief to Keras 3
* setup.py:
  - Add --dev flag for dev tool install
* Fix Keras 3 syntax
* Fix LR Finder for Keras 3
* Fix mixed precision switching for Keras  3
* Add more optimizers + open up config setting
* train: Remove updating FS1 weights to FS2 models
* Alignments: Remove support for legacy .json files
* tools.model:
  - Remove TF Saved Format saving
  - Fix Backup/Restore + Nan-Scan
* Fix inference model creation for Keras 3
* Preview tool: Fix for Keras3
* setup.py: Configure keras backend
* train: Migration of FS2 models to FS3
* Training: Default coverage to 100%
* Remove DirectML backend
* Update setup for MacOS
* GUI: Force line reading to UTF-8
* Remove redundant Tensorflow references
* Remove redundant code
* Legacy model loading: Fix TFLamdaOp scalar ops and DepthwiseConv2D
* Add vertical offset option for training
* Github actions: Add more python versions
* Add python version to workflow names
* Github workflow: Exclude Python 3.12 for macOS
* Implement custom training loop
* Fs3 - Add RTX5xxx and ROCm 6.1-6.4 support (#1511)
* setup.py: Add Cuda/ROCm version select options
* bump minimum python version to 3.11
* Switch from setup.cgf to pyproject.toml
* Documentation: Update all docs to use automodapi
* Allow sysinfo to run with missing packages + correctly install tk under Linux
* Bugfix: dot naming convention in clip models
* lib.config: Centralise globally rather than passing as object
- Add torch DataParallel for multi-gpu training
  - GUI: Group switches together when generating cli args
  - CLI: Remove deprecated multi-character argparse args
  - Refactor:
    - Centralise tensorboard reading/writing + unit tests
    - Create trainer plugin interfaces + add original + distributed
* Update installers
2025-12-21 02:45:11 +00:00

860 lines
32 KiB
Python

#!/usr/bin/env python3
""" CLIP: https://github.com/openai/CLIP. This implementation only ports the visual transformer
part of the model.
"""
# TODO Fix Resnet. It is correct until final MHA
from __future__ import annotations
import inspect
import logging
import typing as T
import sys
import warnings
from dataclasses import dataclass
from keras import layers, ops, Variable, models, saving
import numpy as np
from lib.model.layers import QuickGELU
from lib.utils import get_module_objects, GetModel
if T.TYPE_CHECKING:
from keras import KerasTensor
logger = logging.getLogger(__name__)
TypeModels = T.Literal["RN50", "RN101", "RN50x4", "RN50x16", "RN50x64", "ViT-B-16",
"ViT-B-32", "ViT-L-14", "ViT-L-14-336px", "FaRL-B-16-16", "FaRL-B-16-64"]
@dataclass
class ViTConfig:
""" Configuration settings for ViT
Parameters
----------
embed_dim: int
Dimensionality of the final shared embedding space
resolution: int
Spatial resolution of the input images
layer_conf: tuple[int, int, int, int] | int
Number of layers in the visual encoder, or a tuple of layer configurations for a custom
ResNet visual encoder
width: int
Width of the visual encoder layers
patch: int
Size of the patches to be extracted from the images. Only used for Visual encoder.
git_id: int, optional
The id of the model weights file stored in deepfakes_models repo if they exist. Default: 0
"""
embed_dim: int
resolution: int
layer_conf: int | tuple[int, int, int, int]
width: int
patch: int
git_id: int = 0
def __post_init__(self):
""" Validate that patch_size is given correctly """
assert (isinstance(self.layer_conf, (tuple, list)) and self.patch == 0) or (
isinstance(self.layer_conf, int) and self.patch > 0)
MODEL_CONFIG: dict[TypeModels, ViTConfig] = { # Each model has a different set of parameters
"RN50": ViTConfig(
embed_dim=1024, resolution=224, layer_conf=(3, 4, 6, 3), width=64, patch=0, git_id=21),
"RN101": ViTConfig(
embed_dim=512, resolution=224, layer_conf=(3, 4, 23, 3), width=64, patch=0, git_id=22),
"RN50x4": ViTConfig(
embed_dim=640, resolution=288, layer_conf=(4, 6, 10, 6), width=80, patch=0, git_id=23),
"RN50x16": ViTConfig(
embed_dim=768, resolution=384, layer_conf=(6, 8, 18, 8), width=96, patch=0, git_id=24),
"RN50x64": ViTConfig(
embed_dim=1024, resolution=448, layer_conf=(3, 15, 36, 10), width=128, patch=0, git_id=25),
"ViT-B-16": ViTConfig(
embed_dim=512, resolution=224, layer_conf=12, width=768, patch=16, git_id=26),
"ViT-B-32": ViTConfig(
embed_dim=512, resolution=224, layer_conf=12, width=768, patch=32, git_id=27),
"ViT-L-14": ViTConfig(
embed_dim=768, resolution=224, layer_conf=24, width=1024, patch=14, git_id=28),
"ViT-L-14-336px": ViTConfig(
embed_dim=768, resolution=336, layer_conf=24, width=1024, patch=14, git_id=29),
"FaRL-B-16-16": ViTConfig(
embed_dim=512, resolution=224, layer_conf=12, width=768, patch=16, git_id=30),
"FaRL-B-16-64": ViTConfig(
embed_dim=512, resolution=224, layer_conf=12, width=768, patch=16, git_id=31)}
# ################## #
# VISUAL TRANSFORMER #
# ################## #
class Transformer():
""" A class representing a Transformer model with attention mechanism and residual connections.
Parameters
----------
width: int
The dimension of the input and output vectors.
num_layers: int
The number of layers in the Transformer.
heads: int
The number of attention heads.
attn_mask: :class:`keras.KerasTensor`, optional
The attention mask, by default None.
name: str, optional
The name of the Transformer model, by default "transformer".
Methods
-------
__call__() -> :class:`keras.models.Model`:
Calls the Transformer layers.
"""
_layer_names: dict[str, int] = {}
""" dict[str, int] for tracking unique layer names"""
def __init__(self,
width: int,
num_layers: int,
heads: int,
attn_mask: KerasTensor = None,
name: str = "transformer") -> None:
logger.debug("Initializing: %s (width: %s, num_layers: %s, heads: %s, attn_mask: %s, "
"name: %s)",
self.__class__.__name__, width, num_layers, heads, attn_mask, name)
self._width = width
self._num_layers = num_layers
self._heads = heads
self._attn_mask = attn_mask
self._name = name
logger.debug("Initialized: %s ", self.__class__.__name__)
@classmethod
def _get_name(cls, name: str) -> str:
""" Return unique layer name for requested block.
As blocks can be used multiple times, auto appends an integer to the end of the requested
name to keep all block names unique
Parameters
----------
name: str
The requested name for the layer
Returns
-------
str
The unique name for this layer
"""
cls._layer_names[name] = cls._layer_names.setdefault(name, -1) + 1
name = f"{name}_{cls._layer_names[name]}"
logger.debug("Generating block name: %s", name)
return name
@classmethod
def _mlp(cls, inputs: KerasTensor, key_dim: int, name: str) -> KerasTensor:
"""" Multilayer Perceptron for Block Attention
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input to the MLP
key_dim: int
key dimension per head for MultiHeadAttention
name: str
The name to prefix on the layers
Returns
-------
:class:`keras.KerasTensor`
The output from the MLP
"""
name = f"{name}_mlp"
var_x = layers.Dense(key_dim * 4, name=f"{name}_c_fc")(inputs)
var_x = QuickGELU(name=f"{name}_gelu")(var_x)
var_x = layers.Dense(key_dim, name=f"{name}_c_proj")(var_x)
return var_x
def residual_attention_block(self,
inputs: KerasTensor,
key_dim: int,
num_heads: int,
attn_mask: KerasTensor,
name: str = "ResidualAttentionBlock") -> KerasTensor:
""" Call the residual attention block
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input Tensor
key_dim: int
key dimension per head for MultiHeadAttention
num_heads: int
Number of heads for MultiHeadAttention
attn_mask: :class:`keras.KerasTensor`, optional
Default: ``None``
name: str, optional
The name for the layer. Default: "ResidualAttentionBlock"
Returns
-------
:class:`keras.KerasTensor`
The return Tensor
"""
name = self._get_name(name)
var_x = layers.LayerNormalization(epsilon=1e-05, name=f"{name}_ln_1")(inputs)
var_x = layers.MultiHeadAttention(
num_heads=num_heads,
key_dim=key_dim // num_heads,
name=f"{name}_attn")(var_x, var_x, var_x, attention_mask=attn_mask)
var_x = layers.Add()([inputs, var_x])
var_y = var_x
var_x = layers.LayerNormalization(epsilon=1e-05, name=f"{name}_ln_2")(var_x)
var_x = layers.Add()([var_y, self._mlp(var_x, key_dim, name)])
return var_x
def __call__(self, inputs: KerasTensor) -> KerasTensor:
""" Call the Transformer layers
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input Tensor
Returns
-------
:class:`keras.KerasTensor`
The return Tensor
"""
logger.debug("Calling %s with input: %s", self.__class__.__name__, inputs.shape)
var_x = inputs
for _ in range(self._num_layers):
var_x = self.residual_attention_block(var_x,
self._width,
self._heads,
self._attn_mask,
name=f"{self._name}_resblocks")
return var_x
class EmbeddingLayer(layers.Layer): # pylint:disable=too-many-ancestors,abstract-method
""" Parent class for trainable embedding variables
Parameters
----------
input_shape: tuple[int, ...]
The shape of the variable
scale: int
Amount to scale the random initialization by
name: str
The name of the layer
dtype: str, optional
The datatype for the layer. Mixed precision can mess up the embeddings. Default: "float32"
"""
def __init__(self,
input_shape: tuple[int, ...],
scale: int,
name: str,
*args,
dtype="float32",
**kwargs) -> None:
super().__init__(name=name, dtype=dtype, *args, **kwargs)
self._input_shape = input_shape
self._scale = scale
self._var: KerasTensor
def build(self, input_shape: tuple[int, ...]) -> None:
""" Add the weights
Parameters
----------
input_shape: tuple[int, ...
The input shape of the incoming tensor
"""
self._var = Variable(self._scale * np.random.normal(size=self._input_shape),
trainable=True,
dtype=self.dtype)
super().build(input_shape)
def get_config(self) -> dict[str, T.Any]:
""" Get the config dictionary for the layer
Returns
-------
dict[str, Any]
The config dictionary for the layer
"""
retval = super().get_config()
retval["input_shape"] = self._input_shape
retval["scale"] = self._scale
return retval
class ClassEmbedding(EmbeddingLayer): # pylint:disable=too-many-ancestors,abstract-method
""" Trainable Class Embedding layer """
def call(self, inputs: KerasTensor, *args, **kwargs # pylint:disable=arguments-differ
) -> KerasTensor:
""" Get the Class Embedding layer
Parameters
----------
inputs: :class:`keras.KerasTensor`
Input tensor to the embedding layer
Returns
-------
:class:`keras.KerasTensor`
The class embedding layer shaped for the input tensor
"""
return ops.tile(self._var[None, None], [inputs.shape[0], 1, 1])
class PositionalEmbedding(EmbeddingLayer): # pylint:disable=too-many-ancestors,abstract-method
""" Trainable Positional Embedding layer """
def call(self, inputs: KerasTensor, *args, **kwargs # pylint:disable=arguments-differ
) -> KerasTensor:
""" Get the Positional Embedding layer
Parameters
----------
inputs: :class:`keras.KerasTensor`
Input tensor to the embedding layer
Returns
-------
:class:`keras.KerasTensor`
The positional embedding layer shaped for the input tensor
"""
return ops.tile(self._var[None], [inputs.shape[0], 1, 1])
class Projection(EmbeddingLayer): # pylint:disable=too-many-ancestors,abstract-method
""" Trainable Projection Embedding Layer """
def call(self, inputs: KerasTensor, *args, **kwargs # pylint:disable=arguments-differ
) -> KerasTensor:
""" Get the Projection layer
Parameters
----------
inputs: :class:`keras.KerasTensor`
Input tensor to the embedding layer
Returns
-------
:class:`keras.KerasTensor`
The Projection layer expanded to the batch dimension and transposed for matmul
"""
return ops.tile(ops.transpose(self._var)[None], [inputs.shape[0], 1, 1])
class VisualTransformer():
""" A class representing a Visual Transformer model for image classification tasks.
Parameters
----------
input_resolution: int
The input resolution of the images.
patch_size: int
The size of the patches to be extracted from the images.
width: int
The dimension of the input and output vectors.
num_layers: int
The number of layers in the Transformer.
heads: int
The number of attention heads.
output_dim: int
The dimension of the output vector.
name: str, optional
The name of the Visual Transformer model, Default: "VisualTransformer".
Methods
-------
__call__() -> :class:`keras.models.Model`:
Builds and returns the Visual Transformer model.
"""
def __init__(self,
input_resolution: int,
patch_size: int,
width: int,
num_layers: int,
heads: int,
output_dim: int,
name: str = "VisualTransformer") -> None:
logger.debug("Initializing: %s (input_resolution: %s, patch_size: %s, width: %s, "
"layers: %s, heads: %s, output_dim: %s, name: %s)",
self.__class__.__name__, input_resolution, patch_size, width, num_layers,
heads, output_dim, name)
self._input_resolution = input_resolution
self._patch_size = patch_size
self._width = width
self._num_layers = num_layers
self._heads = heads
self._output_dim = output_dim
self._name = name
logger.debug("Initialized: %s", self.__class__.__name__)
def __call__(self) -> models.Model:
""" Builds and returns the Visual Transformer model.
Returns
-------
:class:`keras.models.Model`
The Visual Transformer model.
"""
inputs = layers.Input([self._input_resolution, self._input_resolution, 3])
var_x: KerasTensor = layers.Conv2D(self._width, # shape = [*, grid, grid, width]
self._patch_size,
strides=self._patch_size,
use_bias=False,
name=f"{self._name}_conv1")(inputs)
var_x = layers.Reshape((-1, self._width))(var_x) # shape = [*, grid ** 2, width]
class_embed = ClassEmbedding((self._width, ),
self._width ** -0.5,
name=f"{self._name}_class_embedding")(var_x)
var_x = layers.Concatenate(axis=1)([class_embed, var_x])
pos_embed = PositionalEmbedding(((self._input_resolution // self._patch_size) ** 2 + 1,
self._width),
self._width ** -0.5,
name=f"{self._name}_positional_embedding")(var_x)
var_x = layers.Add()([var_x, pos_embed])
var_x = layers.LayerNormalization(epsilon=1e-05, name=f"{self._name}_ln_pre")(var_x)
var_x = Transformer(self._width,
self._num_layers,
self._heads,
name=f"{self._name}_transformer")(var_x)
var_x = layers.LayerNormalization(epsilon=1e-05,
name=f"{self._name}_ln_post")(var_x[:, 0, :])
proj = Projection((self._width, self._output_dim),
self._width ** -0.5,
name=f"{self._name}_proj")(var_x)
var_x = layers.Dot(axes=-1)([var_x, proj])
return models.Model(inputs=inputs, outputs=var_x, name=self._name)
# ################ #
# MODIEFIED RESNET #
# ################ #
class Bottleneck():
""" A ResNet bottleneck block that performs a sequence of convolutions, batch normalization,
and ReLU activation operations on an input tensor.
Parameters
----------
inplanes: int
The number of input channels.
planes: int
The number of output channels.
stride: int, optional
The stride of the bottleneck block. Default: 1
name: str, optional
The name of the bottleneck block. Default: "bottleneck"
"""
expansion = 4
""" int: The factor by which the number of input channels is expanded to get the number of
output channels."""
def __init__(self,
inplanes: int,
planes: int,
stride: int = 1,
name: str = "bottleneck") -> None:
logger.debug("Initializing: %s (inplanes: %s, planes: %s, stride: %s, name: %s)",
self.__class__.__name__, inplanes, planes, stride, name)
self._inplanes = inplanes
self._planes = planes
self._stride = stride
self._name = name
logger.debug("Initialized: %s", self.__class__.__name__)
def _downsample(self, inputs: KerasTensor) -> KerasTensor:
""" Perform downsample if required
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input the downsample
Returns
-------
:class:`keras.KerasTensor`
The original tensor, if downsizing not required, otherwise the downsized tensor
"""
if self._stride <= 1 and self._inplanes == self._planes * self.expansion:
return inputs
name = f"{self._name}_downsample"
out = layers.AveragePooling2D(self._stride, name=f"{name}_avgpool")(inputs)
out = layers.Conv2D(self._planes * self.expansion,
1,
strides=1,
use_bias=False,
name=f"{name}_0")(out)
out = layers.BatchNormalization(name=f"{name}_1", epsilon=1e-5)(out)
return out
def __call__(self, inputs: KerasTensor) -> KerasTensor:
""" Performs the forward pass for a Bottleneck block.
All conv layers have stride 1. an avgpool is performed after the second convolution when
stride > 1
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input tensor to the Bottleneck block.
Returns
-------
:class:`keras.KerasTensor`
The result of the forward pass through the Bottleneck block.
"""
out = layers.Conv2D(self._planes, 1, use_bias=False, name=f"{self._name}_conv1")(inputs)
out = layers.BatchNormalization(name=f"{self._name}_bn1", epsilon=1e-5)(out)
out = layers.ReLU()(out)
out = layers.ZeroPadding2D(padding=((1, 1), (1, 1)))(out)
out = layers.Conv2D(self._planes, 3, use_bias=False, name=f"{self._name}_conv2")(out)
out = layers.BatchNormalization(name=f"{self._name}_bn2", epsilon=1e-5)(out)
out = layers.ReLU()(out)
if self._stride > 1:
out = layers.AveragePooling2D(self._stride)(out)
out = layers.Conv2D(self._planes * self.expansion,
1,
use_bias=False,
name=f"{self._name}_conv3")(out)
out = layers.BatchNormalization(name=f"{self._name}_bn3", epsilon=1e-5)(out)
identity = self._downsample(inputs)
out += identity
out = layers.ReLU()(out)
return out
class AttentionPool2d():
""" An Attention Pooling layer that applies a multi-head self-attention mechanism over a
spatial grid of features.
Parameters
----------
spatial_dim: int
The dimensionality of the spatial grid of features.
embed_dim: int
The dimensionality of the feature embeddings.
num_heads: int
The number of attention heads.
output_dim: int
The output dimensionality of the attention layer. If None, it defaults to embed_dim.
name: str
The name of the layer.
"""
def __init__(self,
spatial_dim: int,
embed_dim: int,
num_heads: int,
output_dim: int | None = None,
name="AttentionPool2d"):
logger.debug("Initializing: %s (spatial_dim: %s, embed_dim: %s, num_heads: %s, "
"output_dim: %s, name: %s)",
self.__class__.__name__, spatial_dim, embed_dim, num_heads, output_dim, name)
self._spatial_dim = spatial_dim
self._embed_dim = embed_dim
self._num_heads = num_heads
self._output_dim = output_dim
self._name = name
logger.debug("Initialized: %s", self.__class__.__name__)
def __call__(self, inputs: KerasTensor) -> KerasTensor:
"""Performs the attention pooling operation on the input tensor.
Parameters
----------
inputs: :class:`keras.KerasTensor`:
The input tensor of shape [batch_size, height, width, embed_dim].
Returns
-------
:class:`keras.KerasTensor`:: The result of the attention pooling operation
"""
var_x: KerasTensor
var_x = layers.Reshape((-1, inputs.shape[-1]))(inputs) # NHWC -> N(HW)C
var_x = layers.Concatenate(axis=1)([ops.mean(var_x, axis=1, # N(HW)C -> N(HW+1)C
keepdims=True), var_x])
pos_embed = PositionalEmbedding((self._spatial_dim ** 2 + 1, self._embed_dim), # N(HW+1)C
self._embed_dim ** 0.5,
name=f"{self._name}_positional_embedding")(var_x)
var_x = layers.Add()([var_x, pos_embed])
# TODO At this point torch + keras match. They mismatch after MHA
var_x = layers.MultiHeadAttention(num_heads=self._num_heads,
key_dim=self._embed_dim // self._num_heads,
output_shape=self._output_dim or self._embed_dim,
use_bias=True,
name=f"{self._name}_mha")(var_x[:, :1, ...],
var_x,
var_x)
# only return the first element in the sequence
return var_x[:, 0, ...]
class ModifiedResNet():
""" A ResNet class that is similar to torchvision's but contains the following changes:
- There are now 3 "stem" convolutions as opposed to 1, with an average pool instead of a max
pool.
- Performs anti-aliasing strided convolutions, where an avgpool is prepended to convolutions
with stride > 1
- The final pooling layer is a QKV attention instead of an average pool
Parameters
----------
input_resolution: int
The input resolution of the model. Default is 224.
width: int
The width of the model. Default is 64.
layer_config: list
A list containing the number of Bottleneck blocks for each layer.
output_dim: int
The output dimension of the model.
heads: int
The number of heads for the QKV attention.
name: str
The name of the model. Default is "ModifiedResNet".
"""
def __init__(self,
input_resolution: int,
width: int,
layer_config: tuple[int, int, int, int],
output_dim: int,
heads: int,
name="ModifiedResNet"):
self._input_resolution = input_resolution
self._width = width
self._layer_config = layer_config
self._heads = heads
self._output_dim = output_dim
self._name = name
def _stem(self, inputs: KerasTensor) -> KerasTensor:
""" Applies the stem operation to the input tensor, which consists of 3 convolutional
layers with BatchNormalization and ReLU activation, followed by an average pooling
layer.
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input tensor
Returns
-------
:class:`keras.KerasTensor`
The output tensor after applying the stem operation.
"""
var_x = inputs
for i in range(1, 4):
width = self._width if i == 3 else self._width // 2
strides = 2 if i == 1 else 1
var_x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name=f"conv{i}_padding")(var_x)
var_x = layers.Conv2D(width,
3,
strides=strides,
use_bias=False,
name=f"conv{i}")(var_x)
var_x = layers.BatchNormalization(name=f"bn{i}", epsilon=1e-5)(var_x)
var_x = layers.ReLU()(var_x)
var_x = layers.AveragePooling2D(2, name="avgpool")(var_x)
return var_x
def _bottleneck(self,
inputs: KerasTensor,
planes: int,
blocks: int,
stride: int = 1,
name: str = "layer") -> KerasTensor:
""" A private method that creates a sequential layer of Bottleneck blocks for the
ModifiedResNet model.
Parameters
----------
inputs: :class:`keras.KerasTensor`
The input tensor
planes: int
The number of output channels for the layer.
blocks: int
The number of Bottleneck blocks in the layer.
stride: int
The stride for the first Bottleneck block in the layer. Default is 1.
name: str
The name of the layer. Default is "layer".
Returns
-------
:class:`keras.KerasTensor`
Sequential block of bottlenecks
"""
retval: KerasTensor
retval = Bottleneck(planes, planes, stride, name=f"{name}_0")(inputs)
for i in range(1, blocks):
retval = Bottleneck(planes * Bottleneck.expansion,
planes,
name=f"{name}_{i}")(retval)
return retval
def __call__(self) -> models.Model:
""" Implements the forward pass of the ModifiedResNet model.
Returns
-------
:class:`keras.models.Model`
The modified resnet model.
"""
inputs = layers.Input((self._input_resolution, self._input_resolution, 3))
var_x = self._stem(inputs)
for i in range(4):
stride = 1 if i == 0 else 2
var_x = self._bottleneck(var_x,
self._width * (2 ** i),
self._layer_config[i],
stride=stride,
name=f"{self._name}_layer{i + 1}")
var_x = AttentionPool2d(self._input_resolution // 32,
self._width * 32, # the ResNet feature dimension
self._heads,
self._output_dim,
name=f"{self._name}_attnpool")(var_x)
return models.Model(inputs, outputs=var_x, name=self._name)
# ### #
# VIT #
# ### #
class ViT():
""" Visiual Transform from CLIP
A Convolutional Language-Image Pre-Training (CLIP) model that encodes images and text into a
shared latent space.
Reference
---------
https://arxiv.org/abs/2103.00020
Parameters
----------
name: ["RN50", "RN101", "RN50x4", "RN50x16", "RN50x64", "ViT-B-32",
"ViT-B-16", "ViT-L-14", "ViT-L-14-336px", "FaRL-B_16-64"]
The model configuration to use
input_size: int, optional
The required resolution size for the model. ``None`` for default preset size
load_weights: bool, optional
``True`` to load pretrained weights. Default: ``False``
"""
def __init__(self,
name: TypeModels,
input_size: int | None = None,
load_weights: bool = False) -> None:
logger.debug("Initializing: %s (name: %s, input_size: %s, load_weights: %s)",
self.__class__.__name__, name, input_size, load_weights)
assert name in MODEL_CONFIG, ("Name must be one of %s", list(MODEL_CONFIG))
self._name = name
self._load_weights = load_weights
config = MODEL_CONFIG[name]
self._git_id = config.git_id
res = input_size if input_size is not None else config.resolution
self._net = self._get_vision_net(config.layer_conf,
config.width,
config.embed_dim,
res,
config.patch)
logger.debug("Initialized: %s", self.__class__.__name__)
def _get_vision_net(self,
layer_config: int | tuple[int, int, int, int],
width: int,
embed_dim: int,
resolution: int,
patch_size: int) -> models.Model:
""" Obtain the network for the vision layets
Parameters
----------
layer_config: tuple[int, int, int, int] | int
Number of layers in the visual encoder, or a tuple of layer configurations for a custom
ResNet visual encoder.
width: int
Width of the visual encoder layers.
embed_dim: int
Dimensionality of the final shared embedding space.
resolution: int
Spatial resolution of the input images.
patch_size: int
Size of the patches to be extracted from the images.
Returns
-------
:class:`keras.models.Model`
The :class:`ModifiedResNet` or :class:`VisualTransformer` vision model to use
"""
if isinstance(layer_config, (tuple, list)):
vision_heads = width * 32 // 64
return ModifiedResNet(input_resolution=resolution,
width=width,
layer_config=layer_config,
output_dim=embed_dim,
heads=vision_heads,
name="visual")
vision_heads = width // 64
return VisualTransformer(input_resolution=resolution,
width=width,
num_layers=layer_config,
output_dim=embed_dim,
heads=vision_heads,
patch_size=patch_size,
name="visual")
def __call__(self) -> models.Model:
""" Get the configured ViT model
Returns
-------
:class:`keras.models.Model`
The requested Visual Transformer model
"""
net: models.Model = self._net()
if self._load_weights and not self._git_id:
logger.warning("Trained weights are not available for '%s'", self._name)
return net
if self._load_weights:
model_path = GetModel(f"CLIPv_{self._name}_v1.h5", self._git_id).model_path
logger.info("Loading CLIPv trained weights for '%s'", self._name)
with warnings.catch_warnings():
# TODO There is a potential bug in keras load_weights_by_name that tries to load
# top_level_weights where they don't exist. This always generates a scary looking
# warning, so it supressed for now
warnings.simplefilter("ignore")
# NOTE: Don't load by name as we had to replace local dots with underscores
net.load_weights(model_path, by_name=False, skip_mismatch=True)
return net
# Update layers into Keras custom objects
for name_, obj in inspect.getmembers(sys.modules[__name__]):
if (inspect.isclass(obj) and issubclass(obj, layers.Layer)
and obj.__module__ == __name__):
saving.get_custom_objects().update({name_: obj})
__all__ = get_module_objects(__name__)