#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from NumPyNet.activations import Activations
from NumPyNet.utils import _check_activation
from NumPyNet.utils import check_is_fitted
import numpy as np
from itertools import product
from NumPyNet.exception import LayerError
from NumPyNet.layers.base import BaseLayer
__author__ = ['Mattia Ceccarelli', 'Nico Curti']
__email__ = ['mattia.ceccarelli3@studio.unibo.it', 'nico.curti2@unibo.it']
[docs]class Shortcut_layer(BaseLayer):
'''
Shortcut layer: activation of the linear combination of the output of two layers
layer1 * alpha + layer2 * beta = output
Now working only with same shapes input
Parameters
----------
activation : str or Activation object
Activation function of the layer.
alpha : float, (default = 1.)
first weight of the combination.
beta : float, (default = 1.)
second weight of the combination.
Examples
--------
>>> import pylab as plt
>>>
>>> from NumPyNet import activations
>>>
>>> img_2_float = lambda im : ((im - im.min()) * (1./(im.max() - im.min()) * 1.)).astype(float)
>>> float_2_img = lambda im : ((im - im.min()) * (1./(im.max() - im.min()) * 255.)).astype(np.uint8)
>>>
>>> # Set seed to have same input
>>> np.random.seed(123)
>>>
>>> layer_activ = activations.Relu()
>>>
>>> batch = 2
>>>
>>> alpha = 0.75
>>> beta = 0.5
>>>
>>> # Random input
>>> inpt1 = np.random.uniform(low=-1., high=1., size=(batch, 100, 100, 3))
>>> inpt2 = np.random.uniform(low=-1., high=1., size=inpt1.shape)
>>> b, w, h, c = inpt1.shape
>>>
>>>
>>> # model initialization
>>> layer = Shortcut_layer(activation=layer_activ,
>>> alpha=alpha, beta=beta)
>>>
>>> # FORWARD
>>>
>>> layer.forward(inpt1, inpt2)
>>> forward_out = layer.output.copy()
>>>
>>> print(layer)
>>>
>>> # BACKWARD
>>>
>>> delta = np.zeros(shape=inpt1.shape, dtype=float)
>>> delta_prev = np.zeros(shape=inpt2.shape, dtype=float)
>>>
>>> layer.delta = np.ones(shape=layer.out_shape, dtype=float)
>>> layer.backward(delta, delta_prev)
>>>
>>> # Visualizations
>>>
>>> fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(10, 5))
>>> fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.15)
>>> fig.suptitle('Shortcut Layer\nalpha : {}, beta : {}, activation : {} '.format(alpha, beta, layer_activ.name))
>>>
>>> ax1.imshow(float_2_img(inpt1[0]))
>>> ax1.set_title('Original Image')
>>> ax1.axis('off')
>>>
>>> ax2.imshow(float_2_img(forward_out[0]))
>>> ax2.set_title('Forward')
>>> ax2.axis('off')
>>>
>>> ax3.imshow(float_2_img(delta[0]))
>>> ax3.set_title('Backward')
>>> ax3.axis('off')
>>>
>>> fig.tight_layout()
>>> plt.show()
References
----------
TODO
'''
def __init__(self, activation=Activations, alpha=1., beta=1., **kwargs):
activation = _check_activation(self, activation)
self.activation = activation.activate
self.gradient = activation.gradient
self.alpha, self.beta = alpha, beta
self.ix, self.jx, self.kx = (None, ) * 3
self.iy, self.jy, self.ky = (None, ) * 3
super(Shortcut_layer, self).__init__()
def __str__(self):
(b1, w1, h1, c1), (b2, w2, h2, c2) = self.input_shape
return 'res {:>4d} x{:>4d} x{:>4d} x{:>4d} -> {:>4d} x{:>4d} x{:>4d} x{:>4d}'.format(b2, w2, h2, c2, b1, w1, h1, c1)
def __call__(self, previous_layer):
prev1, prev2 = previous_layer
if prev1.out_shape is None or prev2.out_shape is None:
class_name = self.__class__.__name__
prev_name = layer.__class__.__name__
raise LayerError('Incorrect shapes found. Layer {} cannot be connected to the previous {} layer.'.format(class_name, prev_name))
self.input_shape = [prev1.out_shape, prev2.out_shape]
self._stride_index(prev1.out_shape, prev2.out_shape)
return self
@property
def out_shape(self):
return max(self.input_shape)
def _stride_index(self, shape1, shape2):
'''
Evaluate the strided indexes if the input shapes are different
'''
_, w2, h2, c2 = shape1
_, w1, h1, c1 = shape2
stride = w1 // w2
sample = w2 // w1
stride = stride if stride > 0 else 1
sample = sample if sample > 0 else 1
if not (stride == h1 // h2 and sample == h2 // h1):
class_name = self.__class__.__name__
prev_name = layer.__class__.__name__
raise LayerError('Incorrect shapes found. Layer {} cannot be connected to the previous {} layer.'.format(class_name, prev_name))
idx = product(range(0, w2, sample), range(0, h2, sample), range(0, c2, sample))
self.ix, self.jx, self.kx = zip(*idx)
idx = product(range(0, w1, stride), range(0, w1, stride), range(0, c1, stride))
self.iy, self.jy, self.ky = zip(*idx)
[docs] def forward(self, inpt, prev_output, copy=False):
'''
Forward function of the Shortcut layer: activation of the linear combination between input.
Parameters
----------
inpt : array-like
Input batch of images in format (batch, in_w, in_h, in _c)
prev_output : array-like
second input of the layer
Returns
-------
self
'''
# assert inpt.shape == prev_output.shape
# TODO: find a better solution to initialize the input shape in the constructor
self.input_shape = [inpt.shape, prev_output.shape]
if inpt.shape == prev_output.shape:
self.output = self.alpha * inpt[:] + self.beta * prev_output[:]
else:
# If the layer are combined the smaller one is distributed according to the
# sample stride
# Example:
#
# inpt = [[1, 1, 1, 1], prev_output = [[1, 1],
# [1, 1, 1, 1], [1, 1]]
# [1, 1, 1, 1],
# [1, 1, 1, 1]]
#
# output = [[2, 1, 2, 1],
# [1, 1, 1, 1],
# [2, 1, 2, 1],
# [1, 1, 1, 1]]
# TODO: Not working with different dimensions
if (self.ix, self.jx, self.kx) == (None, None, None):
self._stride_index(inpt.shape, prev_output.shape)
self.output = inpt.copy()
self.output[:, self.ix, self.jx, self.kx] = self.alpha * self.output[:, self.ix, self.jx, self.kx] + self.beta * prev_output[:, self.iy, self.jy, self.ky]
self.output = self.activation(self.output, copy=copy)
self.delta = np.zeros(shape=self.out_shape, dtype=float)
return self
[docs] def backward(self, delta, prev_delta, copy=False):
'''
Backward function of the Shortcut layer
Parameters
----------
delta : array-like
delta array of shape (batch, w, h, c). Global delta to be backpropagated.
delta_prev : array-like
second delta to be backporpagated.
copy : bool (default=False)
States if the activation function have to return a copy of the input or not.
Returns
-------
self
'''
check_is_fitted(self, 'delta')
# derivatives of the activation funtion w.r.t. to input
self.delta *= self.gradient(self.output, copy=copy)
delta[:] += self.delta * self.alpha
if (self.ix, self.iy, self.kx) == (None, None, None): # same shapes
prev_delta[:] += self.delta[:] * self.beta
else: # different shapes
prev_delta[:, self.ix, self.jx, self.kx] += self.beta * self.delta[:, self.iy, self.jy, self.ky]
return self
if __name__ == '__main__':
import pylab as plt
from NumPyNet import activations
img_2_float = lambda im : ((im - im.min()) * (1./(im.max() - im.min()) * 1.)).astype(float)
float_2_img = lambda im : ((im - im.min()) * (1./(im.max() - im.min()) * 255.)).astype(np.uint8)
# Set seed to have same input
np.random.seed(123)
layer_activ = activations.Relu()
batch = 2
alpha = 0.75
beta = 0.5
# Random input
inpt1 = np.random.uniform(low=-1., high=1., size=(batch, 100, 100, 3))
inpt2 = np.random.uniform(low=-1., high=1., size=inpt1.shape)
b, w, h, c = inpt1.shape
# model initialization
layer = Shortcut_layer(activation=layer_activ,
alpha=alpha, beta=beta)
# FORWARD
layer.forward(inpt1, inpt2)
forward_out = layer.output.copy()
print(layer)
# BACKWARD
delta = np.zeros(shape=inpt1.shape, dtype=float)
delta_prev = np.zeros(shape=inpt2.shape, dtype=float)
layer.delta = np.ones(shape=layer.out_shape, dtype=float)
layer.backward(delta, delta_prev)
# Visualizations
fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(10, 5))
fig.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.15)
fig.suptitle('Shortcut Layer\nalpha : {}, beta : {}, activation : {} '.format(alpha, beta, layer_activ.name))
ax1.imshow(float_2_img(inpt1[0]))
ax1.set_title('Original Image')
ax1.axis('off')
ax2.imshow(float_2_img(forward_out[0]))
ax2.set_title('Forward')
ax2.axis('off')
ax3.imshow(float_2_img(delta[0]))
ax3.set_title('Backward')
ax3.axis('off')
fig.tight_layout()
plt.show()