Composable Objective Functions
See original GitHub issueState of the SimPEG
Currently we have a number of Regularization
and DataMisfit
classes which implement parts of the objective function. These are kept separate and explicitly combined in the InvProblem
.
dmis = DataMisfit.l2_DataMisfit(survey)
reg = Regularization.Tikhonov(mesh)
invProb = InvProblem.BaseInvProblem(dmis, reg, opt)
In the Tikhonov
regularization there are a number of fitting parameters:
alpha_s
, alpha_x
, alpha_y
, alpha_z
, alpha_xx
, alpha_yy
, and alpha_zz
these in turn combined with a linear operator (derivative or volume term):
Wx
, Wy
, Wz
, Wsmall
, Wxx
, Wyy
, Wzz
, and Wsmooth2
finally these are put together in a single operator W
, which allows you combine in a single call:
weighted_residual = W * (mapping * (m - mref))
obj_function = 0.5 * r.dot(r)
Not only is this repeated seven times in a single class, the idea is also repeated throughout the DataMisfit
classes as well! This means if we implement something like total variation, we have to duplicate that code in a lot of places.
Yikes.
What I would like to talk about in this issue is that we can break up the Regularization
classes into a bunch of composable ObjectiveFunction
s which can be added together and multiplied by scalars (the alpha
). These should also be used by the DataMisfit
classes.
Bugs in the current idea
- The
alpha
s andW
s are super repetitive - We cannot really do joint inversions (multiple physics, or properties)
- Higher order regularizations should be composable (e.g.
Tikhonov
) - There is only a single
beta
applied in the inversion class, this is not extensible
Current Workarounds
There is a MultiRegularization
class made by @sgkang in the SIP folder which allows multiple models to be regularized at the same time.
You can get around a lot of it by doing InjectActiveCell
maps or writing other custom Regularization
classes. e.g. in joint inversion for volume and slowness:
http://gif.eos.ubc.ca/sites/default/files/HeaglySEG2014.pdf
Other discussions
This was also brought up in the SimPEG meeting by @fourndo on October 4th, 2016 https://youtu.be/A_oy9w8J0q4?t=24m18s This goes through some of the group discussions on composable objective functions.
Some constraints
- Keep the
Tikhonov
idea, although this may turn into a function rather than a class - Share code between the
DataMisfit
andRegularization
- Make use of the
properties
library so ensure we can validate and pickle (think parallel) - Compose through addition and multiply scalars - or directives in the future (think beta)
Sketch at the implementation
dmis = Objective.DataMisfit_L2(survey)
reg = Objective.tikhonov(mesh)
beta = 1e-2
objfun = dmis + beta * reg
invProb = InvProblem.BaseInvProblem(objfun, opt)
You can see above that the functions would be composable through +
and *
. Here the tikhonov
becomes a function rather than a class.
def tikhonov(mesh, alpha_s, alpha_x, etc):
return (
alpha_s * Smallness(mesh) +
alpha_x * Smoothness(mesh, direction='x') +
etc
)
Similar to the Maps
classes, there would be a CompositeObjective
function. That can handle addition or multiplication with certain things.
Examples
Weighting objectives
For example, similar to the BetaEstimate_ByEig
we could initialize the estimated weights for an objective function (through the eigenvalues).
beta1, beta2 = Directives.InitializeEstimatedWeights(
[dmis1, dmis2, reg], # These are the objective functions to weight
[1.0, 2.0, 1e2] # These would be the ratios
)
objfun = 1.0 * dmis1 + (beta1 * dmis2) + (beta2 * reg)
Note here that beta1
would be a InversionDirective
that would need to be called on startup.
Block coordinate descent algorithm
Combined with the weighting we might want the ability to completely turn off the objective until later in the algorithm.
coords = Directives.CoordinateDescent(2)
objfun = (coords.block[0] * dmis1) + (coords.block[1] * dmis2) + reg
assert coords.block[0] == 1 and coords.block[0] == 0
coords.next()
assert coords.block[0] == 0 and coords.block[0] == 1
We probably want the ability for Directive.Float()
to multiply with other objectives. That can come later…
Cross gradient
If we are weighting two models that should have similar contours, we could use a cross-gradient approach.
xgrad = Directives.CrossGradient(wires.sigma, wires.porosity)
objfun = dmis + beta * tikhonov(mesh) + xgrad
Other proposed changes
I would also propose the change that the .eval
functions become .__call__
. Similarly, the .evalDeriv
and .eval2Deriv
could become .deriv
and .deriv2
which would be closer to the Maps
class as well.
f = objfun(model)
g = objfun.deriv(model)
H = objfun.deriv2(model)
This would be a first step at moving towards a better minimize
package that is interoperable with scipy
and scikit-learn
.
Summary
The above implementation would allow for composing multiple data objective functions and regularizations. We would work towards joint inversions and more complicated inversion schemes that are directed by reusable pieces. This would open the door for a lot of new things, and actually make a step towards reproducible joint inversions.
Issue Analytics
- State:
- Created 7 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
So I had developed something very close to this for some of my own stuff that might be able to be extended for this. The basic idea is:
Classes that extend this ObjectiveFunction class would use the init method in their own class to set the information they need to evaluate, then overwrite the 3 functions f, d, and H
This closed with #511