Hyperparameters#

In the Beta-cooling schedule page we made use of the hyperparams.CooledMultiplier to easily implement the beta cooling schedule through an object that knows how to cool down* itself.

The framework offers classes for such objects, that we call hyperparameters or dynamic hyperparameters. They work as their underlying values (a float or a Numpy array), but they also have some mechanisms to easily update those values when needed. Usually the updating process happens when their update method gets called (see CooledMultiplier.update as an example).

Scalar hyperparameters#

We’ve seen how the hyperparams.CooledMultiplier represents a float that can be updated throughout an inversion. That class inherits from the inversion_ideas.base.Multiplier, which simply wraps a float value and implements all the required interface to behave like one.

For example, we can instantiate it with a given value:

import inversion_ideas as ii

multiplier = ii.base.Multiplier(3.14)
print(multiplier)
Multiplier(3.14)

This multiplier holds the 3.14 float in its value attribute:

multiplier.value
3.14

We can use that multiplier object as any other float. For example, to perform any aritmetic operation with another float:

print(multiplier + 3.0)
print(multiplier - 1.0)
print(multiplier * 2.0)
print(multiplier / 2.0)
print(multiplier**3)
6.140000000000001
2.14
6.28
1.57
30.959144000000002

Important

Note that when we operate a Multiplier with a float we obtain a new float, not another Multiplier object.

We can also use it to operate with Numpy arrays:

import numpy as np

array = np.array([1., 2., 3., 4.])
multiplier * array
array([ 3.14,  6.28,  9.42, 12.56])

Important

Note that when we operate a Multiplier with a Numpy array we obtain a new array with the resulting values of the operation. Modifying the Multiplier after the operation will not change the value of the resulting array.

The only exception is when we multiply it by an Objective function. In such case, the resulting Scaled function will hold the whole Multiplier as its Scaled.multiplier, instead of only its value as a float.

smallness = ii.SimpleSmallness(n_params=10)

scaled = multiplier * smallness
print(scaled)
3.14 φ0(m)
# Check that the multiplier in the scaled objective function is
# our `multiplier` object:
scaled.multiplier is multiplier
True

Array hyperparameters#

Analougsly, we can wrap a Numpy array inside a base.WrappedArray:

wrapped = ii.base.WrappedArray(np.array([1., 2., 3.]))
wrapped
WrappedArray([1., 2., 3.])

The wrapped array lives in its array attribute:

wrapped.array
array([1., 2., 3.])

Similarly, we can operate with this object as if it were a regular Numpy array:

print(wrapped * 2.0)
print(wrapped / 2.0)
[2. 4. 6.]
[0.5 1.  1.5]
wrapped * np.array([10., 20., 30.])
array([10., 40., 90.])
matrix = np.arange(9).reshape(3, 3)
print(matrix)

matrix @ wrapped
[[0 1 2]
 [3 4 5]
 [6 7 8]]
array([ 8., 26., 44.])

Important

Note that when we operate with a WrappedArray we obtain a new array with the resulting values of the operation. Modifying the WrappedArray after the operation will not change the value of the resulting array.

We can also pass it to Numpy functions:

np.mean(wrapped)
np.float64(2.0)

And even index it:

wrapped[0]
np.float64(1.0)
wrapped[:2]
array([1., 2.])