Gradient Optimization
Gradient optimization is the continuous-parameter optimization path used by the COBRA subsystem.
The current source provides three registered gradient optimizers:
implemented under:
Numerical-gradient utilities live in:
The gradient family is used by:
when:
and the selected kernel advertises:
What gradient optimization tunes
The gradient subsystem minimizes a scalar objective:
In COBRA, \(x\) usually represents aggregation parameters rather than the coefficients of the original base estimators.
Examples include:
GradientCOBRA
x = [bandwidth]
MixCOBRA one-parameter mode
x = [bandwidth-like parameter]
MixCOBRA two-parameter mode
x = [alpha, beta]
The objective itself is the cross-validation loss produced by the fitted COBRA aggregation pipeline.
Position in the pipeline
flowchart LR
X["Parameter vector"]
A["Adapter"]
K["Kernel"]
G["Aggregator"]
L["Cross-validation loss"]
N["Numerical gradient"]
O["Gradient optimizer"]
X --> A --> K --> G --> L
L --> N
N --> O
O --> X
The optimizer does not directly train:
Instead, it repeatedly evaluates how changing the adapter parameters affects the cross-validation objective.
Registered gradient optimizers
| Registry name | Class | Update strategy |
|---|---|---|
gd |
GradientDescentOptimizer |
plain gradient descent |
momentum |
MomentumOptimizer |
gradient descent with velocity |
adam |
AdamOptimizer |
adaptive first/second moments |
All three are registered with categories:
through:
BaseGradientOptimizer
All gradient implementations inherit:
which provides:
gradient calculation
learning-rate schedules
parameter initialization
main optimization loop
early stopping
history tracking
Subclasses only need to implement:
Constructor
The current base constructor is:
BaseGradientOptimizer(
learning_rate=0.01,
max_iter=300,
tol=1e-7,
speed="constant",
gradient_method="central",
eps=1e-7,
n_tries=5,
init_range=(1e-4, 3.0),
show_process=True,
**kwargs,
)
Core parameters
| Parameter | Current default | Purpose |
|---|---|---|
learning_rate |
0.01 |
initial learning-rate scale |
max_iter |
300 |
maximum optimizer iterations |
tol |
1e-7 |
gradient-norm stopping threshold |
speed |
"constant" |
learning-rate schedule |
gradient_method |
"central" |
numerical-gradient method |
eps |
1e-7 |
finite-difference step |
n_tries |
5 |
initialization candidates |
init_range |
(1e-4, 3.0) |
initialization interval |
show_process |
True |
progress display |
Gradient computation
The base optimizer calls:
compute_gradient(
objective=objective,
params=params,
gradient=grad_fn,
method=self.gradient_method,
eps=self.eps,
)
If:
is supplied, it overrides numerical differentiation.
Otherwise, the requested numerical-gradient method is used.
Supported numerical-gradient methods
The current dispatcher supports:
through:
Central difference
The default method is:
For parameter coordinate \(i\):
The source temporarily perturbs one coordinate at a time.
Implementation concept:
p[i] = original + eps
f_plus = objective(p)
p[i] = original - eps
f_minus = objective(p)
grad[i] = (
f_plus
-
f_minus
) / (
2 * eps
)
Central-difference cost
For a parameter vector of dimension:
one gradient calculation requires approximately:
objective evaluations.
Since a COBRA objective itself runs cross-validation, numerical gradients can be computationally expensive even for low-dimensional parameter vectors.
Forward difference
The source also supports:
with:
The implementation evaluates:
once, then one positive perturbation per dimension.
Approximate cost:
objective calls per gradient evaluation.
SPSA
The method:
uses Simultaneous Perturbation Stochastic Approximation.
The source samples:
then computes:
SPSA cost
Only two objective evaluations are required regardless of parameter dimension:
This can make SPSA attractive for higher-dimensional black-box objectives.
SPSA reproducibility caveat
The current helper uses:
from NumPy's global random state.
It does not accept:
and does not use the COBRA estimator's seed.
Current source behavior
Selecting:
introduces stochastic perturbations whose sequence is not controlled by
the normal estimator random_state parameter.
Complex-step gradient
The method:
uses:
The source default inside the standalone function is:
Important dispatcher detail
BaseGradientOptimizer passes:
into:
and its default is:
Therefore when complex-step differentiation is selected through the optimizer, the effective epsilon is normally:
unless you explicitly configure another value.
The standalone helper's own:
default is bypassed because compute_gradient() supplies the optimizer
epsilon.
Complex compatibility
The objective must support complex-valued parameter perturbations.
The source does not test that compatibility before running.
If downstream code discards imaginary components or rejects complex arrays, the method may not produce a meaningful gradient.
Parallel central difference
The method:
uses the same central-difference formula as the default method but evaluates coordinates with:
and:
Worker count
compute_gradient() has:
and, for parallel mode, maps that to:
unless a value is explicitly provided.
However, BaseGradientOptimizer.gradient() currently calls
compute_gradient() without passing an n_jobs argument.
Therefore the standard optimizer path uses:
for the parallel gradient helper.
Unknown gradient methods
If the method name is not one of:
the dispatcher raises:
Analytical gradients
The optimization API supports:
in:
When present:
returns:
and skips numerical differentiation.
High-level COBRA usage
The current GradientCOBRA and MixCOBRA optimization paths call the optimizer without supplying a custom:
so they use numerical gradients.
Analytical gradients are available as a lower-level optimizer feature rather than a high-level estimator feature in the current source.
Initialization
When:
the base optimizer runs:
Initialization grid
The source builds:
from:
then constructs:
and chooses the candidate with the lowest objective score.
Initialization limitation
Inside:
the source always calls:
when init_param is absent.
So the automatic initialization path is effectively one-dimensional from this entry point.
For multi-parameter optimization, a caller should provide an explicit
init_param.
The high-level MixCOBRA source does exactly that.
COBRA initial parameters
GradientCOBRA uses:
for gradient mode.
MixCOBRA one-parameter mode uses:
and two-parameter mode uses:
Therefore the internal automatic initialization search is normally bypassed in the main COBRA integrations.
Initial gradient scaling
After choosing x, the optimizer computes:
Then:
This is an important implementation detail.
The value configured as:
is divided by the initial gradient norm before the learning-rate schedule is applied.
Effective first step scale
For constant schedule:
So the first step size is normalized by the initial gradient magnitude.
The optimizer does not directly use:
as the first raw multiplier.
Learning-rate schedules
The source supports:
through:
Current formulas
The source dictionary is:
{
"constant": lambda x, y: y,
"linear": lambda x, y: x * y,
"log": lambda x, y: np.log(1 + x) * y,
"sqrt_root": lambda x, y: np.sqrt(1 + x) * y,
"quad": lambda x, y: (1 + x**2) * y,
"exp": lambda x, y: np.exp(x) * y,
}
where:
Important schedule behavior
Several names may sound like decay schedules, but the current formulas do not decay.
For example:
linear
lr_t = t * r0
log
lr_t = log(1+t) * r0
sqrt_root
lr_t = sqrt(1+t) * r0
quad
lr_t = (1+t²) * r0
exp
lr_t = exp(t) * r0
These increase with iteration.
linear starts at zero
At:
the linear schedule returns:
So the first update under:
does not move the parameters.
Exponential growth
With:
the learning rate becomes:
This can grow extremely quickly.
The current source does not cap or clip the scheduled learning rate.
Current implementation
The non-constant schedules are increasing functions as implemented.
Do not assume names such as linear, log, or sqrt_root mean learning
rate decay.
Unknown schedule names
The source uses:
Therefore an unknown:
does not raise an error.
It silently uses:
behavior.
Main optimization loop
After initialization, the optimizer stores:
Then it runs up to:
iterations.
For iteration t:
1. compute scheduled learning rate
2. call subclass step()
3. repair NaN parameter vectors
4. compute new gradient
5. evaluate new objective
6. update best solution
7. check gradient-norm stopping
8. optionally shrink r0 after gradient sign changes
9. record history
NaN parameter repair
After the subclass update:
the source checks:
So if any coordinate becomes NaN, the entire new parameter vector is
replaced by:
Infinite values are not repaired
The check is specifically:
The source does not separately repair:
parameter values.
Best-solution tracking
After evaluating:
the source updates:
The optimizer returns the best point encountered, not necessarily the final iteration point.
Early stopping
The gradient is recomputed at:
and stopping occurs when:
The exact check is:
History omission on early stopping
This stopping check happens before the history entry is appended.
Therefore the final converged step is not added to:
when optimization terminates through this gradient-norm condition.
The score can still update:
before the break.
Gradient-sign adjustment
After at least four previous iterations:
So if any gradient coordinate changes sign, the base learning-rate scale is reduced by:
Sign comparison detail
The comparison is between:
and:
not directly between grad_new and the current grad.
At the end of each completed iteration:
so after normal completed iterations those values become identical for the next loop.
This means the effective comparison is against the prior stored gradient.
Optimization history
A completed iteration appends:
{
"iter": t + 1,
"x": x.copy(),
"score": score,
"grad": grad,
"grad_norm": float(
np.linalg.norm(
grad
)
),
"lr": lr_t,
}
History fields
| Field | Meaning |
|---|---|
iter |
one-based iteration number |
x |
parameter vector after update |
score |
objective value at that vector |
grad |
gradient at that vector |
grad_norm |
Euclidean gradient norm |
lr |
scheduled step multiplier used for the update |
History is not total objective-call history
A finite-difference gradient can call the objective multiple times per iteration.
Those internal calls are not recorded as history entries.
The following are also evaluated outside normal history rows:
initial gradient
initial best-score calculation
terminating early-stop step
initialization candidate search, when used
So:
is the number of recorded optimizer iterations, not the total number of objective evaluations.
Progress display
When:
the optimizer wraps the iteration range in:
This description remains:
even for:
because the progress loop is implemented in the shared base class.
Progress text
After a completed iteration, the description is changed to:
Gradient Descent
The simplest registered optimizer is:
under:
Its update rule is:
The implementation is exactly:
The state dictionary is unchanged.
Direct use
import numpy as np
from kfc_procedure.cobra.core.optimizers import (
GradientDescentOptimizer,
)
def objective(
params,
):
x = params[0]
return (
x - 2.0
) ** 2
optimizer = GradientDescentOptimizer(
learning_rate=0.1,
max_iter=100,
show_process=False,
)
result = optimizer.optimize(
objective,
init_param=np.array([
1.0
]),
)
print(
result["x"]
)
print(
result["score"]
)
Momentum
MomentumOptimizer is registered as:
and adds:
to the base optimizer parameters.
Momentum state
On the first step:
The state stores one velocity vector:
Momentum update
The source computes:
then:
Implementation:
Configure momentum
optimizer = MomentumOptimizer(
momentum=0.95,
learning_rate=0.05,
max_iter=200,
show_process=False,
)
The current constructor does not validate that:
or any other interval.
The supplied value is used directly.
Adam
AdamOptimizer is registered as:
and adds:
to the base configuration.
Adam state
On first use, the source creates:
The state therefore tracks:
Adam moment updates
The source computes:
and:
Bias correction
The implementation applies:
and:
Adam update
The source uses:
The stabilizer:
is hard-coded inside:
No configurable Adam epsilon
The class docstring mentions an epsilon numerical stability constant, but the
constructor is:
There is no explicit:
parameter.
The update always uses:
in the current source.
Documentation/source mismatch
The class documentation mentions an Adam epsilon parameter, but the current constructor does not expose one.
Configure Adam
optimizer = AdamOptimizer(
beta1=0.9,
beta2=0.999,
learning_rate=0.05,
max_iter=200,
gradient_method="central",
show_process=False,
)
The current source does not explicitly validate the ranges of:
OptimizerFactory usage
Create gradient descent:
from kfc_procedure.cobra.core.optimizers import (
OptimizerFactory,
)
optimizer = OptimizerFactory.create(
"gd",
learning_rate=0.05,
)
Momentum:
Adam:
Inspect gradient optimizers
The current registry should include:
Kernel compatibility
GradientCOBRA and MixCOBRA do not blindly use gradient optimization.
Before choosing the optimizer family, they check:
Gradient-compatible built-in kernels
The current continuous kernels advertise:
including:
Non-gradient kernels
The current compact/discrete kernels advertise:
including:
Automatic fallback to grid search
If:
but:
both GradientCOBRA and MixCOBRA change the local effective method to:
before optimizer resolution.
So:
does not continue with Adam under the normal source path.
It falls back to grid-family optimization.
Category validation
For GradientCOBRA and MixCOBRA, effective:
mode requires:
So:
is rejected when the effective method remains gradient-based.
Likewise, grid mode requires the:
category.
GradientCOBRA configuration
The relevant constructor parameters include:
To enable gradient optimization:
from kfc_procedure.cobra import (
GradientCOBRA,
)
model = GradientCOBRA(
optimizer="adam",
opt_method="grad",
learning_rate=0.05,
max_iter=200,
)
GradientCOBRA optimizer parameters
The source copies:
then, in gradient mode, updates:
before creating the optimizer.
Therefore top-level:
take precedence over same-named values previously placed in
optimizer_params.
Additional GradientCOBRA optimizer settings
Use optimizer_params for options such as:
optimizer_params={
"tol": 1e-6,
"gradient_method": "forward",
"eps": 1e-6,
"speed": "constant",
"show_process": False,
"beta1": 0.9,
"beta2": 0.999,
}
Parameters irrelevant to a given subclass may be absorbed by **kwargs
depending on the constructor path.
GradientCOBRA starting point
The current gradient path calls the optimizer with:
so bandwidth optimization starts from:
GradientCOBRA result
After optimization:
and the source stores:
self.optimization_outputs_ = {
"method": method,
"optimizer": optimizer,
"bandwidth": self.bandwidth_,
"score": result["score"],
"history": history_df,
"evaluations": len(
result["history"]
),
}
GradientCOBRA records effective method
The stored:
uses the local effective method.
Therefore if the kernel forces a fallback from:
to:
GradientCOBRA reports:
MixCOBRA configuration
Relevant constructor parameters include:
optimizer="grid"
optimizer_params=None
opt_method="grid"
learning_rate=0.01
max_iter=300
one_parameter=False
Gradient mode example:
from kfc_procedure.cobra import (
MixCOBRARegressor,
)
model = MixCOBRARegressor(
optimizer="adam",
opt_method="grad",
learning_rate=0.01,
)
MixCOBRA one-parameter start
With:
the gradient optimizer starts from:
MixCOBRA two-parameter start
With:
the optimizer starts from:
representing:
MixCOBRA result
The optimization output stores:
{
"method": self.opt_method,
"score": result["score"],
"history": history_df,
"evaluations": len(
result["history"]
),
"params": result["x"],
}
MixCOBRA reporting caveat
The current source stores:
rather than the local effective method after kernel fallback.
Therefore a model configured with:
can still report:
even if a non-gradient kernel caused the actual path to use grid optimization.
This differs from GradientCOBRA.
No positivity constraints
The base gradient optimizer performs ordinary unconstrained vector updates.
There is no generic source-level projection such as:
or clipping to a configured interval.
Therefore parameters such as:
can become negative during gradient optimization.
Why this matters
For the one-parameter adapter:
A negative:
turns non-negative distances into negative adapted values.
With the default RBF kernel:
this can produce weights larger than one:
when:
The current optimizer does not prevent this.
Current gradient path is unconstrained
Grid search uses an explicitly positive default candidate range
0.001 to 10.0.
Gradient optimization is not restricted to that interval.
No general parameter clipping
The same applies to MixCOBRA:
Gradient mode can produce:
because no projection step is implemented.
The source leaves those parameter semantics to the objective and optimizer trajectory.
Objective smoothness
The gradient path uses finite differences over the full cross-validation objective.
That objective contains several components:
Whether the numerical gradient behaves smoothly depends on the entire composed pipeline.
Hard operations and gradient compatibility
The high-level source uses:
as its explicit compatibility gate.
However, this flag describes the kernel only.
Other parts of the objective can still contain non-smooth behavior, for example:
The current optimizer does not perform symbolic differentiability analysis of the whole objective.
Gradient methods are black-box approximations
The finite-difference helpers only need:
They do not require access to analytical derivatives of:
This makes the gradient subsystem effectively a black-box numerical optimizer.
Central difference with COBRA CV
For one GradientCOBRA bandwidth:
central difference approximates:
where each:
is itself a full mean cross-validation loss.
Two-parameter central difference
For MixCOBRA:
central difference requires approximately four full objective evaluations per gradient:
The optimizer then separately evaluates the objective at the new parameter vector.
eps sensitivity
The default finite-difference step is:
If the objective changes very little at that scale, numerical cancellation can make the estimated gradient noisy.
If the objective has abrupt changes, a tiny epsilon can sample two points on different sides of a discontinuity.
The current source exposes:
through optimizer_params, so it can be tuned manually.
Example with forward differences
model = GradientCOBRA(
optimizer="gd",
opt_method="grad",
learning_rate=0.05,
optimizer_params={
"gradient_method": "forward",
"eps": 1e-5,
"show_process": False,
},
)
Example with SPSA
model = MixCOBRARegressor(
optimizer="adam",
opt_method="grad",
optimizer_params={
"gradient_method": "spsa",
"eps": 1e-4,
"show_process": False,
},
)
Remember that the current SPSA helper is not seeded from:
Example with parallel central differences
model = MixCOBRARegressor(
optimizer="adam",
opt_method="grad",
optimizer_params={
"gradient_method": "parallel",
"show_process": False,
},
)
The standard BaseGradientOptimizer.gradient() call does not expose a custom
n_jobs, so the parallel helper uses its fallback worker count:
Custom analytical gradient
At lower optimizer level:
def objective(
x,
):
return (
x[0] - 3.0
) ** 2
def gradient(
x,
):
return np.array([
2.0
*
(
x[0] - 3.0
)
])
Then:
optimizer = GradientDescentOptimizer(
learning_rate=0.1,
show_process=False,
)
result = optimizer.optimize(
objective,
init_param=np.array([
1.0
]),
grad_fn=gradient,
)
The numerical-gradient dispatcher is bypassed.
Learning-rate schedule example
For experimentation, the current source also accepts:
but remember that these formulas increase rather than decay.
Gradient Descent vs Momentum vs Adam
| Property | GD | Momentum | Adam |
|---|---|---|---|
| state | none | velocity v |
m, v, t |
| default extra params | none | momentum=0.9 |
beta1=0.9, beta2=0.999 |
| adaptive coordinate scaling | No | No | Yes |
| bias correction | No | No | Yes |
| base numerical gradients | Yes | Yes | Yes |
| same stopping logic | Yes | Yes | Yes |
| same learning-rate scheduler | Yes | Yes | Yes |
Inspect optimization history
For GradientCOBRA:
Gradient history is converted to a pandas DataFrame.
Typical columns include:
for one-dimensional optimization.
MixCOBRA history
For two parameters, the history conversion names coordinates:
while retaining:
evaluations is not objective-call count
The high-level estimators set:
This is the number of recorded optimizer iterations.
It is not the number of cross-validation objective evaluations.
With central differences, actual objective calls can be many times larger.
Reconstruct a gradient manually
For a fitted GradientCOBRA model, the numerical helper can be used directly.
Conceptually:
from kfc_procedure.cobra.core.optimizers._utils import (
central_difference_gradient,
)
x = np.array([
model.bandwidth_
])
grad = central_difference_gradient(
model.kappa_cross_validation_error,
x,
eps=1e-7,
)
This evaluates the fitted model's CV objective around the chosen bandwidth.
Debugging gradient optimization
Inspect configured method
Inspect configured optimizer
Inspect resolved optimizer
Inspect gradient method
Inspect epsilon
Inspect tolerance
Inspect learning-rate schedule
Inspect selected parameters
GradientCOBRA:
MixCOBRA:
Check for negative parameters
GradientCOBRA:
MixCOBRA:
This is useful because the current gradient path is unconstrained.
Debugging convergence
Inspect:
Useful patterns include:
but the source itself does not classify convergence quality.
Debugging missing final iteration
If the returned best score seems better than the final row in history, remember that a gradient-norm early-stop step is evaluated before the history append.
The terminating point can therefore be returned as:
without appearing as the last DataFrame row.
Custom gradient optimizer
To implement a new update rule, subclass:
and implement:
Example:
import numpy as np
from kfc_procedure.cobra.core.optimizers import (
OptimizerFactory,
)
from kfc_procedure.cobra.core.optimizers.gradient import (
BaseGradientOptimizer,
)
@OptimizerFactory.register(
"sign_gd",
categories={
"optimizer",
"gradient",
},
)
class SignGradientOptimizer(
BaseGradientOptimizer
):
def step(
self,
x,
lr,
grad,
state,
):
x_new = (
x
-
lr
*
np.sign(
grad
)
)
return (
x_new,
state,
)
This automatically inherits:
from the base class.
Custom optimizer result contract
The inherited optimize() method returns:
which matches the result contract expected by GradientCOBRA and MixCOBRA.
Current source caveats
| Area | Current behavior |
|---|---|
| default gradient estimator | central difference |
| numerical methods | central, forward, SPSA, complex, parallel |
| analytical gradient support | lower-level grad_fn only |
| optimizer start in GradientCOBRA | [1.0] |
| optimizer start in 2D MixCOBRA | [1.0, 1.0] |
| parameter bounds | none |
| positive bandwidth enforcement | none |
| NaN parameter recovery | replace with 0.95 * x |
| Inf recovery | none |
| stopping condition | new gradient norm < tol |
| early-stop step in history | No |
| non-constant schedules | increasing formulas |
| unknown schedule | silently uses constant |
| SPSA seed | global NumPy RNG |
| parallel gradient workers | effectively -1 in standard optimizer path |
| Adam epsilon | hard-coded 1e-8 |
| progress label | starts as "GD" for all gradient optimizers |
| MixCOBRA fallback reporting | may report configured rather than effective method |
Quick reference
| Goal | Configuration |
|---|---|
| vanilla gradient descent | optimizer="gd", opt_method="grad" |
| momentum | optimizer="momentum", opt_method="grad" |
| Adam | optimizer="adam", opt_method="grad" |
| central finite differences | gradient_method="central" |
| cheaper forward differences | gradient_method="forward" |
| stochastic two-evaluation estimate | gradient_method="spsa" |
| complex-step estimate | gradient_method="complex" |
| parallel central differences | gradient_method="parallel" |
| disable progress | show_process=False |
| stop earlier | increase tol |
| more iterations | increase max_iter |
| change difference step | set eps |
Mental model
Gradient optimization treats the complete COBRA cross-validation pipeline as a black-box scalar function and numerically follows parameter directions that reduce that loss.
For GradientCOBRA:
For two-parameter MixCOBRA:
The current implementation provides GD, Momentum, and Adam update rules over the same shared numerical-gradient and stopping machinery.