Skip to content

Commit

Permalink
cleanup Dox, fix deprecations
Browse files Browse the repository at this point in the history
  • Loading branch information
Kautenja committed Feb 11, 2018
1 parent 32cb0da commit 266a118
Show file tree
Hide file tree
Showing 14 changed files with 496 additions and 989 deletions.
202 changes: 29 additions & 173 deletions content-reconstruction.ipynb

Large diffs are not rendered by default.

20 changes: 9 additions & 11 deletions neural_stylization/loss_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,24 @@ def content_loss(content, combination):
return 0.5 * K.sum(K.square(combination - content))


def gram(matrix):
def gram(x):
"""
Return a gram matrix for the given input matrix.
Args:
matrix: the matrix to calculate the gram matrix of
x: the matrix to calculate the gram matrix of
Returns: the gram matrix of `matrix`
Returns:
the gram matrix of x
"""
# flatten the 3D tensor by converting each filter's 2D matrix of points
# to a vector. thus we have the matrix:
# [filter_width x filter_height, num_filters]
g = K.reshape(matrix, (matrix.shape[0] * matrix.shape[1], matrix.shape[2]))
F = K.reshape(x, (x.shape[0] * x.shape[1], x.shape[2]))
# take inner product over all the vectors to produce the Gram matrix over
# the number of filters
g = K.dot(K.transpose(g), g)
# TODO: test this with an image that is taller than wider to ensure the
# directionality of the dot operation translates
return g
return K.dot(K.transpose(F), F)


def style_loss(style, combination):
Expand All @@ -53,14 +51,14 @@ def style_loss(style, combination):
"""
# M_l is the width times the height of the current layer
Ml_2 = int(style.shape[0] * style.shape[1])**2
Ml2 = int(style.shape[0] * style.shape[1])**2
# N_l is the number of distinct filters in the layer
Nl_2 = int(style.shape[2])**2
Nl2 = int(style.shape[2])**2

# take the squared euclidean distance between the gram matrices of both
# the style and combination image. divide by the constant scaling factor
# based on parameterized sizes
return K.sum(K.square(gram(style) - gram(combination))) / (4 * Nl_2 * Ml_2)
return K.sum(K.square(gram(style) - gram(combination))) / (4 * Nl2 * Ml2)


__all__ = ['content_loss', 'style_loss']
4 changes: 4 additions & 0 deletions neural_stylization/optimizers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""Black Box optimization methods."""
from .sgd import SGD
from .l_bfgs import L_BFGS


# export the public API for this package
__all__ = ['SGD', 'L_BFGS']
26 changes: 17 additions & 9 deletions neural_stylization/optimizers/l_bfgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def __init__(self, max_evaluations: int=20) -> None:
Args:
max_evaluations: how fast to adjust the parameters (dW)
Returns: None
Returns:
None
"""
self.max_evaluations = max_evaluations
self._loss = None
Expand All @@ -28,34 +30,38 @@ def __repr__(self) -> str:
self.max_evaluations
])

def loss(self, X):
def loss(self, X) -> np.ndarray:
"""Calculate the loss given some X."""
# make sure this is called _only after gradients_
assert self._loss is None
# calculate and store both the loss and the gradients
loss, gradients = self.loss_and_gradients(X)
# update the cache for this pass
self._loss = loss
self._gradients = gradients

return self._loss

def gradients(self, _):
def gradients(self, X) -> np.ndarray:
"""Calculate the gradients (lazily) given some X."""
# make sure this is called _only after loss_
assert self._loss is not None
# copy the gradients and nullify the cache
# copy the gradients
gradients = np.copy(self._gradients)
# nullify the cache for this pass
self._loss = None
self._gradients = None

return gradients

def minimize(self,
def __call__(self,
X: np.ndarray,
shape: tuple,
loss_grads: Callable,
iterations: int=1000,
callback: Callable=None):
callback: Callable=None) -> np.ndarray:
"""
Reduce the loss geanerated by X.
Reduce the loss generated by X.
Args:
X: the input value to adjust to minimize loss
Expand All @@ -65,9 +71,11 @@ def minimize(self,
iterations: the number of iterations of optimization to perform
callback: an optional callback method to receive image updates
Returns: an optimized X about the loss and gradients given
"""
Returns:
an optimized X about the loss and gradients given
"""
# create a loss and gradients evaluation method for SciPy
def loss_and_gradients(X):
"""Calculate the loss and gradients with appropriate reshaping."""
loss, gradients = loss_grads([X.reshape(shape)])
Expand Down
12 changes: 8 additions & 4 deletions neural_stylization/optimizers/sgd.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ def __init__(self, learning_rate: float=1e-4) -> None:
Args:
learning_rate: how fast to adjust the parameters (dW)
Returns: None
Returns:
None
"""
self.learning_rate = learning_rate

Expand All @@ -25,14 +27,14 @@ def __repr__(self) -> str:
self.learning_rate
])

def minimize(self,
def __call__(self,
X: np.ndarray,
shape: tuple,
loss_grads: Callable,
iterations: int=1000,
callback: Callable=None):
"""
Reduce the loss geanerated by X by moving it based on its gradient.
Reduce the loss generated by X by moving it based on its gradient.
Args:
X: the input value to adjust to minimize loss
Expand All @@ -42,7 +44,9 @@ def minimize(self,
iterations: the number of iterations of optimization to perform
callback: an optional callback method to receive image updates
Returns: an optimized X about the loss and gradients given
Returns:
an optimized X about the loss and gradients given
"""
for i in tqdm(range(iterations)):
# pass the input through the loss function and generate gradients
Expand Down
21 changes: 12 additions & 9 deletions neural_stylization/reconstruct_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@
def reconstruct_content(content_path: str,
image_shape: tuple=None,
layer_name: str='block4_conv2',
optimizer: 'optimizers.Optimizer'=L_BFGS(),
optimize: Callable=L_BFGS(),
iterations: int=10,
noise_range: tuple=(0, 1),
callback: Callable=None) -> Image:
"""
Reconstruct the given content image at the given VGG19 layer.
Args:
content_path: the path to the content image to reconstruct
layer_name: the layer to reconstruct the content from
optimizer: the optimizer for minimizing the content loss
optimize: the optimization method for minimizing the content loss
iterations: the number of iterations to run the optimizer
noise_range: the range of values for initializing random noise
callback: the callback for iterations of gradient descent
Returns:
the reconstructed content image based on the VGG19 response
at the given layer name
"""
# load the image with the given shape (or the default shape if there is
# no shape provided)
Expand All @@ -42,15 +45,15 @@ def reconstruct_content(content_path: str,
# ImageNet dataset
content = normalize(content)

# load the content image into keras as a constant, it never changes
# load the content image into Keras as a constant, it never changes
content = K.constant(content, name='Content')
# create a placeholder for the trained image, this variable trains
canvas = K.placeholder(content.shape, name='Canvas')
# combine the content and canvas tensors along the frame axis (0) into a
# 4D tensor of shape [2, height, width, channels]
tensor = K.concatenate([content, canvas], axis=0)
input_tensor = K.concatenate([content, canvas], axis=0)
# build the model with the 4D input tensor of content and canvas
model = VGG_19(include_top=False, input_tensor=tensor, pooling='avg')
model = VGG_19(include_top=False, input_tensor=input_tensor, pooling='avg')

# extract the layer's out that we have interest in for reconstruction
layer = model[layer_name]
Expand All @@ -63,16 +66,16 @@ def reconstruct_content(content_path: str,
# generate the iteration function for gradient descent optimization
step = K.function([canvas], [loss, grads])

# generate random noise
noise = np.random.uniform(0, 1, content.shape)
# generate random noise with the given noise range
noise = np.random.uniform(*noise_range, size=content.shape)

# optimize the white noise to reconstruct the content
image = optimizer.minimize(noise, canvas.shape, step, iterations, callback)
image = optimize(noise, canvas.shape, step, iterations, callback)

# clear the Keras session
K.clear_session()

# de-normalize the image (from ImageNet means) and convert back to binary
# denormalize the image (from ImageNet means) and convert back to binary
return matrix_to_image(denormalize(image.reshape(canvas.shape)[0]))


Expand Down
38 changes: 20 additions & 18 deletions neural_stylization/reconstruct_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ def reconstruct_style(style_path: str,
'block4_conv1',
'block5_conv1'
],
optimizer: 'optimizers.Optimizer'=L_BFGS(),
optimize: Callable=L_BFGS(),
iterations: int=10,
noise_range: tuple=(0, 1),
callback: Callable=None) -> Image:
"""
Reconstruct the given content image at the given VGG19 layer.
Expand All @@ -33,11 +34,13 @@ def reconstruct_style(style_path: str,
layer_name: the layer to reconstruct the content from
optimizer: the optimizer for minimizing the content loss
iterations: the number of iterations to run the optimizer
noise_range: the range of values for initializing random noise
callback: the callback for iterations of gradient descent
Returns:
the reconstructed content image based on the VGG19 response
at the given layer name
the reconstructed content image based on the VGG19 response at the
given layer name
"""
# load the image with the given shape (or the default shape if there is
# no shape provided)
Expand All @@ -54,26 +57,25 @@ def reconstruct_style(style_path: str,
canvas = K.placeholder(style.shape, name='Canvas')
# combine the style and canvas tensors along the frame axis (0) into a
# 4D tensor of shape [2, height, width, channels]
tensor = K.concatenate([style, canvas], axis=0)
input_tensor = K.concatenate([style, canvas], axis=0)
# build the model with the 4D input tensor of style and canvas
model = VGG_19(include_top=False, input_tensor=tensor, pooling='avg')
model = VGG_19(include_top=False, input_tensor=input_tensor, pooling='avg')

# initialize the loss since we need to accumulate it iteratively
# initialize the loss to accumulate iteratively over the layers
loss = K.variable(0.0)

# iterate over the list of all the layers that we want to include
for layer_name in layer_names:
# extract the layer's out that we have interest in for reconstruction
layer = model[layer_name]
# calculate the loss between the output of the layer on the
# style (0) and the canvas (1). The style loss needs to know
# the size of the image as well by width (shape[2]) and height
# (shape[1])
loss += style_loss(layer[0], layer[1])

# Gatys et al. use a w_l of 1/5 for their example with the 5 layers.
# As such, we'll simply and say for any length of layers, just take
# the average. (mirroring what they did)
# calculate the loss between the output of the layer on the style (0)
# and the canvas (1). The style loss needs to know the size of the
# image as well by width (shape[2]) and height (shape[1])
loss = loss + style_loss(layer[0], layer[1])

# Gatys et al. use a w_l of 1/5 for their example with the 5 layers. As
# such, we'll simply and say for any length of layers, just take the
# average. (mirroring what they did)
loss /= len(layer_names)

# calculate the gradients
Expand All @@ -82,15 +84,15 @@ def reconstruct_style(style_path: str,
step = K.function([canvas], [loss, grads])

# generate random noise
noise = np.random.uniform(0, 1, canvas.shape)
noise = np.random.uniform(*noise_range, size=canvas.shape)

# optimize the white noise to reconstruct the content
image = optimizer.minimize(noise, canvas.shape, step, iterations, callback)
image = optimize(noise, canvas.shape, step, iterations, callback)

# clear the Keras session
K.clear_session()

# de-normalize the image (from ImageNet means) and convert back to binary
# denormalize the image (from ImageNet means) and convert back to binary
return matrix_to_image(denormalize(image.reshape(canvas.shape)[0]))


Expand Down
Loading

0 comments on commit 266a118

Please sign in to comment.