jcm/models/lpips.py (61 lines of code) (raw):

# Code from https://github.com/pcuenca/lpips-j/blob/main/src/lpips_j/lpips.py # # Original copyright statement: # Copyright 2021 The DALL·E mini Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import h5py import flax.linen as nn import jax.numpy as jnp # from flaxmodels import VGG16 import flaxmodels.vgg as vgg from huggingface_hub import hf_hub_download class VGGExtractor(vgg.VGG): """ VGG16 configured as a feature extractor for LPIPS, with weights downloaded from the huggingface hub. Note: subclasses `VGG` from `flaxmodels`, even though it was probably not meant to be subclassed (is not included in __all__). """ def __init__(self): super().__init__( output="activations", pretrained="imagenet", architecture="vgg16", include_head=False, ) def setup(self): weights_file = hf_hub_download( repo_id="pcuenq/lpips-jax", filename="vgg16_weights.h5" ) self.param_dict = h5py.File(weights_file, "r") class NetLinLayer(nn.Module): weights: jnp.array kernel_size = (1, 1) def setup(self): w = lambda *_: self.weights self.layer = nn.Conv( 1, self.kernel_size, kernel_init=w, strides=None, padding=0, use_bias=False ) def __call__(self, x): x = self.layer(x) return x class LPIPS(nn.Module): def setup(self): # We don't add a scaling layer because `VGG16` already includes it self.feature_names = ["relu1_2", "relu2_2", "relu3_3", "relu4_3", "relu5_3"] self.vgg = VGGExtractor() weights_file = hf_hub_download( repo_id="pcuenq/lpips-jax", filename="lpips_lin.h5" ) lin_weights = h5py.File(weights_file) self.lins = [ NetLinLayer(jnp.array(lin_weights[f"lin{i}"])) for i in range(len(self.feature_names)) ] def __call__(self, x, t): x = self.vgg((x + 1) / 2) t = self.vgg((t + 1) / 2) feats_x, feats_t, diffs = {}, {}, {} for i, f in enumerate(self.feature_names): feats_x[i], feats_t[i] = normalize_tensor(x[f]), normalize_tensor(t[f]) diffs[i] = (feats_x[i] - feats_t[i]) ** 2 # We should maybe vectorize this better res = [ spatial_average(self.lins[i](diffs[i]), keepdims=True) for i in range(len(self.feature_names)) ] val = res[0] for i in range(1, len(res)): val += res[i] return val def normalize_tensor(x, eps=1e-10): # Use `-1` because we are channel-last norm_factor = jnp.sqrt(jnp.sum(x**2, axis=-1, keepdims=True)) return x / (norm_factor + eps) def spatial_average(x, keepdims=True): # Mean over W, H return jnp.mean(x, axis=[1, 2], keepdims=keepdims)