Modeling protocol¶
In order to make hyperparameter optimization, george comes with a modeling language specification. To this end, any component of the model that is specified by parameters that we might want to fit should conform to a simple modeling protocol. Each element should expose a value, a parameter vector, and the gradient of the value with respect to these parameters. Some places where this comes in handy is for the hyperparameters of the kernels and the metrics.
The protocol¶
We don’t really care too much about the implementation details of your objects but if you want them to satisfy this protocol, they must satisfy all of the following methods:
import numpy as np
class ModelingProtocol(object):
def __len__(self):
"""
Return the number of (un-frozen) parameters exposed by this
object.
"""
return 10
def get_parameter_names(self):
"""
Return a list with the names of the parameters.
"""
return ["param1", "param2", ...]
def get_vector(self):
"""
Returns a numpy array with the current settings of all the
non-frozen parameters.
"""
return np.array([self.par1, self.par2, ...])
def set_vector(self, vector):
"""
Update the vector with a numpy array of the same shape and order
as ``get_vector``.
"""
self.par1 = vector[0]
self.par2 = vector[1]
...
def get_value(self, *args):
"""
Get the value of this object at the current setting of the
parameters. This method can optionally take arguments. For a
kernel, these extra arguments would be the input coordinates.
"""
return f(*args)
def get_gradient(self, *args):
"""
Get the gradient of ``get_value`` with respect to the parameter
vector returned by ``get_vector``.
"""
return dfdx(*args)
def freeze_parameter(self, parameter_name):
"""
Fix the value of some parameter by name at its current value. This
parameter should no longer be returned by ``get_vector`` or
``get_gradient``.
"""
freeze(parameter_name)
def thaw_parameter(self, parameter_name):
"""
The opposite of ``freeze_parameter``.
"""
thaw(parameter_name)
A simple example¶
Let’s start with a simple example. Let’s say that we want to implement a one-dimensional exponential-squared kernel
in pure-Python and fit for the logarithms of the parameters. In that case, the implementation would be something like the following:
import numpy as np
class MyNewExpSquared(object):
def __init__(self, a, l):
self.parameter_names = ["lna", "lnl"]
self.parameters = np.array([a, l])
self.unfrozen = np.ones_like(self.parameters, dtype=bool)
def __len__(self):
return np.sum(self.unfrozen)
def get_parameter_names(self):
return [n for i, n in enumerate(self.parameter_names)
if self.unfrozen[i]]
def get_vector(self):
return np.log(self.parameters[self.unfrozen])
def set_vector(self, vector):
self.parameters[self.unfrozen] = np.exp(vector)
def get_value(self, x1, x2):
a, l = self.parameters
r2 = (x1 - x2)**2
return a * np.exp(-0.5 * r2 / l)
def get_gradient(self, x1, x2):
a, l = self.parameters
value = self.get_value(x1, x2)
grad = np.array((
value,
value * (0.5 * (x1 - x2)**2 / l)
))
return grad[self.unfrozen]
def freeze_parameter(self, parameter_name):
self.unfrozen[self.parameter_names.index(parameter_name)] = False
def thaw_parameter(self, parameter_name):
self.unfrozen[self.parameter_names.index(parameter_name)] = True