Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/source/reproducible.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,47 @@ configuration file and use it the script like so:
# Set the seed for numpy
np.random.seed(config.seed)
# application-specific seed setting

Instantiating objects
---------------------

Another cool feature of Hydra is `object instantiating <https://hydra.cc/docs/advanced/instantiate_objects/overview/>`_.
Imagine you want to try different optimizers for your Deep Neural Network (DNN) or you want to try different DNNs in the same pipeline.
Instead of doing ``if-else`` statements, you write one line of code and let Hydra choose the
appropriate object class based on your configuration. See the script
`examples/real_convolve.py <https://github.com/ebezzam/python-dev-tips/blob/main/examples/real_convolve.py>`_
for the example.

.. code-block:: python

@hydra.main(version_base=None, config_path="configs", config_name="defaults")
def main(config):
# instantiate object from config
signal = instantiate(config.signal)
# application specific choice of object class

``instantiate`` function from ``hydra.utils`` allows you to define an object in a YAML file
without being tied to a particular class. To do this, you need to define ``_target_`` in
your config (see configs in ``configs/signal``) and object initialization arguments. Object class
can be either defined in your project (``configs/signal/ExampleZeros``, ``configs/signal/ExampleCustom``)
or taken from a package (``configs/signal/ExampleNumpy``).

Note that here we use another Hydra feature: config grouping and splitting. Instead of writing
configurations for all objects in the main config and copying configuration files, we create a sub-directory ``signal``,
where all ``signal`` configs are defined. Now we can run the main config with the ``signal`` of
our choice simply by specifying it in the command line. For example, ``python examples/real_convolve.py signal=ExampleNumpy``
or ``python examples/real_convolve.py signal=ExampleZeros``.

If we need to define some of the arguments inside the code before creating an object, we can pass them directly to the ``instantiate`` function.
For example, we did not define ``signal_len`` in the ``signal`` configuration file and passed it by hand:
``signal = instantiate(config.signal, config.signal_len)``. This is especially useful when you have positional-only arguments
like ``numpy.random.randn`` in our example. Note that we can both define arguments in the configuration file and pass new ones to ``instantiate`` like we did for
``ExampleCustom``.

Object instantiating is recursive, i.e. some of the arguments of the class can also be
defined using ``_target_`` and they will be created automatically. For example,
``python examples/real_convolve.py signal=ExampleCustom +signal/transform=power`` defines the ``transform`` argument of
the ``ExampleCustom`` class as the ``PowerTransform`` class. The ``+signal/transform=power`` in the command line
means adding the ``transform`` argument to the current ``signal`` configuration from the ``power.yaml`` config defined
in ``configs/signal/transform``. That is, you can have sub-sub-directories. The default values from sub-sub-directories
can also be changed in the command-line: ``python examples/real_convolve.py signal=ExampleCustom +signal/transform=power signal.transform.pow=3``
4 changes: 4 additions & 0 deletions examples/configs/defaults.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
defaults:
- signal: ExampleNumpy
- _self_

hydra:
job:
chdir: True # change to output folder
Expand Down
2 changes: 2 additions & 0 deletions examples/configs/signal/ExampleCustom.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_target_: real_convolve.ExampleCustom
numpy_method: arange
1 change: 1 addition & 0 deletions examples/configs/signal/ExampleNumpy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_target_: numpy.random.randn
1 change: 1 addition & 0 deletions examples/configs/signal/ExampleZeros.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_target_: real_convolve.ExampleZeros
2 changes: 2 additions & 0 deletions examples/configs/signal/transform/power.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_target_: real_convolve.PowerTransform
pow: 2
52 changes: 51 additions & 1 deletion examples/real_convolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,67 @@
import matplotlib.pyplot as plt
from pydevtips.fftconvolve import RFFTConvolve
import hydra
from hydra.utils import instantiate
import os


class PowerTransform:
def __init__(self, pow):
self.pow = pow

def __call__(self, x):
return np.power(x, self.pow)


class ExampleZeros:
"""
Wrapper over np.zeros
"""

def __init__(self, signal_len) -> None:
self.data = np.zeros(signal_len)

def max(self):
return self.data.max()

def __array__(self):
return self.data

def __len__(self):
return len(self.data)


class ExampleCustom:
"""
Wrapper over custom np.ndarray creation method with an optional transform
"""

def __init__(self, signal_len, numpy_method, transform=None) -> None:
self.data = getattr(np, numpy_method)(signal_len)
if transform is not None:
self.data = transform(self.data)

def max(self):
return self.data.max()

def __array__(self):
return self.data

def __len__(self):
return len(self.data)


@hydra.main(version_base=None, config_path="configs", config_name="defaults")
def main(config):

output_dir = os.getcwd()
np.random.seed(config.seed)

# Create a signal
signal = np.random.randn(config.signal_len)
signal = instantiate(config.signal, config.signal_len)
# Note that we added extra argument signal_len which was not defined in signal config
print(f"Signal class, len and max: {type(signal), len(signal), signal.max()}")
signal = np.array(signal) # for the following computations np.ndarray is required

# Create a moving average filter (low pass)
n = config.filter_len
Expand Down