|
- #! /usr/bin/python
- # -*- coding: utf-8 -*-
-
- import tensorflow as tf
- from tensorlayer import logging
- from tensorlayer.files import utils
- from tensorlayer.layers.core import Module
-
- __all__ = [
- 'Lambda',
- 'ElementwiseLambda',
- ]
-
-
- class Lambda(Module):
- """A layer that takes a user-defined function using Lambda.
- If the function has trainable weights, the weights should be provided.
- Remember to make sure the weights provided when the layer is constructed are SAME as
- the weights used when the layer is forwarded.
- For multiple inputs see :class:`ElementwiseLambda`.
-
- Parameters
- ----------
- fn : function
- The function that applies to the inputs (e.g. tensor from the previous layer).
- fn_weights : list
- The trainable weights for the function if any. Optional.
- fn_args : dict
- The arguments for the function if any. Optional.
- name : str or None
- A unique layer name.
-
- Examples
- ---------
- Non-parametric and non-args case:
- This case is supported in the Model.save() / Model.load() to save / load the whole model architecture and weights(optional).
-
- >>> x = tl.layers.Input([8, 3], name='input')
- >>> y = tl.layers.Lambda(lambda x: 2*x, name='lambda')(x)
-
-
- Non-parametric and with args case:
- This case is supported in the Model.save() / Model.load() to save / load the whole model architecture and weights(optional).
-
- >>> def customize_func(x, foo=42): # x is the inputs, foo is an argument
- >>> return foo * x
- >>> x = tl.layers.Input([8, 3], name='input')
- >>> lambdalayer = tl.layers.Lambda(customize_func, fn_args={'foo': 2}, name='lambda')(x)
-
-
- Any function with outside variables:
- This case has not been supported in Model.save() / Model.load() yet.
- Please avoid using Model.save() / Model.load() to save / load models that contain such Lambda layer. Instead, you may use Model.save_weights() / Model.load_weights() to save / load model weights.
- Note: In this case, fn_weights should be a list, and then the trainable weights in this Lambda layer can be added into the weights of the whole model.
-
- >>> a = tl.ops.Variable(1.0)
- >>> def func(x):
- >>> return x + a
- >>> x = tl.layers.Input([8, 3], name='input')
- >>> y = tl.layers.Lambda(func, fn_weights=[a], name='lambda')(x)
-
-
- Parametric case, merge other wrappers into TensorLayer:
- This case is supported in the Model.save() / Model.load() to save / load the whole model architecture and weights(optional).
-
- >>> layers = [
- >>> tl.layers.Dense(10, act=tl.Relu),
- >>> tl.layers.Dense(5, act=tl.Relu),
- >>> tl.layers.Dense(1, activation=tf.identity)
- >>> ]
- >>> perceptron = tl.layers.SequentialLayer(layers)
- >>> # in order to compile keras model and get trainable_variables of the keras model
- >>> _ = perceptron(np.random.random([100, 5]).astype(np.float32))
- >>>
- >>> class CustomizeModel(tl.layers.Module):
- >>> def __init__(self):
- >>> super(CustomizeModel, self).__init__()
- >>> self.dense = tl.layers.Dense(in_channels=1, n_units=5)
- >>> self.lambdalayer = tl.layers.Lambda(perceptron, perceptron.trainable_variables)
- >>>
- >>> def forward(self, x):
- >>> z = self.dense(x)
- >>> z = self.lambdalayer(z)
- >>> return z
- >>>
- >>> optimizer = tl.optimizers.Adam(learning_rate=0.1)
- >>> model = CustomizeModel()
- >>> model.set_train()
- >>>
- >>> for epoch in range(50):
- >>> with tf.GradientTape() as tape:
- >>> pred_y = model(data_x)
- >>> loss = tl.cost.mean_squared_error(pred_y, data_y)
- >>>
- >>> gradients = tape.gradient(loss, model.trainable_weights)
- >>> optimizer.apply_gradients(zip(gradients, model.trainable_weights))
-
- """
-
- def __init__(
- self,
- fn,
- fn_weights=None,
- fn_args=None,
- name=None,
- ):
-
- super(Lambda, self).__init__(name=name)
- self.fn = fn
- self._trainable_weights = fn_weights if fn_weights is not None else []
- self.fn_args = fn_args if fn_args is not None else {}
-
- try:
- fn_name = repr(self.fn)
- except:
- fn_name = 'name not available'
- logging.info("Lambda %s: func: %s, len_weights: %s" % (self.name, fn_name, len(self._trainable_weights)))
-
- self.build()
- self._built = True
-
- def __repr__(self):
- s = '{classname}('
- s += 'fn={fn_name},'
- s += 'len_weights={len_weights},'
- s += 'name=\'{name}\''
- s += ')'
- try:
- fn_name = repr(self.fn)
- except:
- fn_name = 'name not available'
- return s.format(
- classname=self.__class__.__name__, fn_name=fn_name, len_weights=len(self._trainable_weights),
- **self.__dict__
- )
-
- def build(self, inputs_shape=None):
- pass
-
- def forward(self, inputs, **kwargs):
-
- if len(kwargs) == 0:
- outputs = self.fn(inputs, **self.fn_args)
- else:
- outputs = self.fn(inputs, **kwargs)
-
- return outputs
-
- def get_args(self):
- init_args = {}
- if isinstance(self.fn, tf.keras.layers.Layer) or isinstance(self.fn, tf.keras.Model):
- init_args.update({"layer_type": "keraslayer"})
- init_args["fn"] = utils.save_keras_model(self.fn)
- init_args["fn_weights"] = None
- if len(self._nodes) == 0:
- init_args["keras_input_shape"] = []
- else:
- init_args["keras_input_shape"] = self._nodes[0].in_tensors[0].get_shape().as_list()
- else:
- init_args = {"layer_type": "normal"}
- return init_args
-
-
- class ElementwiseLambda(Module):
- """A layer that use a custom function to combine multiple :class:`Layer` inputs.
- If the function has trainable weights, the weights should be provided.
- Remember to make sure the weights provided when the layer is constructed are SAME as
- the weights used when the layer is forwarded.
-
- Parameters
- ----------
- fn : function
- The function that applies to the inputs (e.g. tensor from the previous layer).
- fn_weights : list
- The trainable weights for the function if any. Optional.
- fn_args : dict
- The arguments for the function if any. Optional.
- name : str or None
- A unique layer name.
-
- Examples
- --------
-
- Non-parametric and with args case
- This case is supported in the Model.save() / Model.load() to save / load the whole model architecture and weights(optional).
-
- >>> def func(noise, mean, std, foo=42):
- >>> return mean + noise * tf.exp(std * 0.5) + foo
- >>> noise = tl.layers.Input([100, 1])
- >>> mean = tl.layers.Input([100, 1])
- >>> std = tl.layers.Input([100, 1])
- >>> out = tl.layers.ElementwiseLambda(fn=func, fn_args={'foo': 84}, name='elementwiselambda')([noise, mean, std])
-
-
- Non-parametric and non-args case
- This case is supported in the Model.save() / Model.load() to save / load the whole model architecture and weights(optional).
-
- >>> noise = tl.layers.Input([100, 1])
- >>> mean = tl.layers.Input([100, 1])
- >>> std = tl.layers.Input([100, 1])
- >>> out = tl.layers.ElementwiseLambda(fn=lambda x, y, z: x + y * tf.exp(z * 0.5), name='elementwiselambda')([noise, mean, std])
-
-
- Any function with outside variables
- This case has not been supported in Model.save() / Model.load() yet.
- Please avoid using Model.save() / Model.load() to save / load models that contain such ElementwiseLambda layer. Instead, you may use Model.save_weights() / Model.load_weights() to save / load model weights.
- Note: In this case, fn_weights should be a list, and then the trainable weights in this ElementwiseLambda layer can be added into the weights of the whole model.
-
- >>> vara = [tf.Variable(1.0)]
- >>> def func(noise, mean, std):
- >>> return mean + noise * tf.exp(std * 0.5) + vara
- >>> noise = tl.layers.Input([100, 1])
- >>> mean = tl.layers.Input([100, 1])
- >>> std = tl.layers.Input([100, 1])
- >>> out = tl.layers.ElementwiseLambda(fn=func, fn_weights=vara, name='elementwiselambda')([noise, mean, std])
-
- """
-
- def __init__(
- self,
- fn,
- fn_weights=None,
- fn_args=None,
- name=None, #'elementwiselambda',
- ):
-
- super(ElementwiseLambda, self).__init__(name=name)
- self.fn = fn
- self._trainable_weights = fn_weights if fn_weights is not None else []
- self.fn_args = fn_args if fn_args is not None else {}
-
- try:
- fn_name = repr(self.fn)
- except:
- fn_name = 'name not available'
- logging.info(
- "ElementwiseLambda %s: func: %s, len_weights: %s" % (self.name, fn_name, len(self._trainable_weights))
- )
-
- self.build()
- self._built = True
-
- def __repr__(self):
- s = '{classname}('
- s += 'fn={fn_name},'
- s += 'len_weights={len_weights},'
- s += 'name=\'{name}\''
- s += ')'
- try:
- fn_name = repr(self.fn)
- except:
- fn_name = 'name not available'
- return s.format(
- classname=self.__class__.__name__, fn_name=fn_name, len_weights=len(self._trainable_weights),
- **self.__dict__
- )
-
- def build(self, inputs_shape=None):
- # do nothing
- # the weights of the function are provided when the Lambda layer is constructed
- pass
-
- # @tf.function
- def forward(self, inputs, **kwargs):
-
- if not isinstance(inputs, list):
- raise TypeError(
- "The inputs should be a list of values which corresponds with the customised lambda function."
- )
-
- if len(kwargs) == 0:
- outputs = self.fn(*inputs, **self.fn_args)
- else:
- outputs = self.fn(*inputs, **kwargs)
-
- return outputs
|