""" Image style reconstruction with Gram matrices. https://en.wikipedia.org/wiki/Gram_matrix https://en.wikipedia.org/wiki/Neural_style_transfer https://arxiv.org/pdf/1603.08155#page=7&zoom=auto,-294,3 """ import numpy as np def gram_matrix(mat: np.ndarray) -> np.ndarray: """ Returns the Gram (Gramian) matrix of an image. :param mat: matrix of shape (C, H, W); C = color channels, H = height, W = width. :type mat: np.ndarray :return: matrix of shape (C, C). :rtype: np.ndarray Examples -------- >>> gram_matrix(np.ones((2,5,5))) array([[0.5, 0.5], [0.5, 0.5]]) >>> gram_matrix(np.ones((3,5,5))) array([[0.33333333, 0.33333333, 0.33333333], [0.33333333, 0.33333333, 0.33333333], [0.33333333, 0.33333333, 0.33333333]]) >>> gram_matrix(np.ones((3,5,5))).shape (3, 3) """ color, height, width = mat.shape vec = mat.reshape(color, height * width) gram = vec @ vec.T return gram / (color * height * width) def gram_loss(input_features: np.ndarray, reference_features: np.ndarray) -> np.float64: """ Calculates the squared Frobenius norm of the difference between the Gram matrices of the input and reference image. :param input_features: Feature map of shape (C, H, W) :type input_features: np.ndarray :param reference_features: Feature map of shape (C, H, W) :type reference_features: np.ndarray :return: Gram loss between the two feature maps. :rtype: float64 Examples -------- >>> a = np.random.randn(3,5,5) >>> gram_loss(a, a) np.float64(0.0) >>> a = np.zeros((3,5,5)) >>> b = np.ones((3,5,5)) >>> gram_loss(a, b) np.float64(1.0) """ input_gram = gram_matrix(input_features) reference_gram = gram_matrix(reference_features) return np.sum(np.square(input_gram - reference_gram)).astype(np.float64) if __name__ == "__main__": import doctest doctest.testmod()