Weighted Mean
WeightedMeanCombiner is a supervised regression combiner used by the KFC
C-Step.
It learns a linear relationship between the F-Step prediction matrix and the true regression target.
The implementation is registered under:
and lives in:
The source describes it as:
Weighted mean combiner (regression).
Learns optimal linear weights using Ordinary Least Squares (OLS).
Core idea
Assume the F-Step produces a prediction matrix
where:
- \(n\) is the number of observations;
- \(K\) is the number of prediction columns.
In KFC, those columns correspond to divergence-specific prediction families.
The source models the target as
where:
contains the learned coefficients.
Conceptually:
F-Step predictions
│
▼
┌──────────────────────────────┐
│ euclidean gkl logistic │
├──────────────────────────────┤
│ 12.4 12.8 12.1 │
│ 20.7 21.0 20.4 │
│ 8.3 8.1 8.5 │
└──────────────────────────────┘
│
▼
LinearRegression
│
▼
learned coefficients
│
▼
final regression prediction
Constructor
The current implementation is:
The constructor creates:
So the only combiner-specific constructor parameter is:
with default:
Default model
With the default:
the learned rule is approximately:
If:
then the model becomes:
Important: this is not a constrained weighted average
The name weighted_mean can suggest a convex weighted average, but the current
source does not impose such constraints.
The learned coefficients are produced by ordinary:
and are not constrained to satisfy:
or:
Therefore coefficients may be:
A more precise description of the current implementation is:
rather than a mathematically constrained weighted mean.
Fit behavior
The implementation is:
def fit(
self,
X,
y,
):
X = np.asarray(X)
if X.ndim != 2:
raise ValueError(
f"Expected 2D array, got {X.shape}"
)
self.model.fit(
X,
y,
)
return self
There are three important behaviors here:
- input is converted with
np.asarray(); Xmust be two-dimensional;- the internal
LinearRegressionis fitted directly onXandy.
Required input shape
The expected shape is:
In KFC:
For example, with four divergences:
Invalid one-dimensional input
This is invalid:
because the source explicitly checks:
and raises:
Even if you have only one prediction source, the matrix should still have shape:
rather than:
Prediction behavior
WeightedMeanCombiner.combine() is:
Because BaseCombiner.predict() delegates to:
both of these are equivalent:
and:
Direct example
import numpy as np
from kfc_procedure.core.combiner.regression import (
WeightedMeanCombiner,
)
P_train = np.array([
[10.0, 10.5, 9.8],
[12.0, 11.8, 12.3],
[18.0, 18.4, 17.9],
[25.0, 24.7, 25.2],
])
y_train = np.array([
10.2,
12.1,
18.3,
25.0,
])
combiner = WeightedMeanCombiner(
fit_intercept=False,
)
combiner.fit(
P_train,
y_train,
)
P_test = np.array([
[14.0, 14.2, 13.9],
[20.0, 19.8, 20.3],
])
y_pred = combiner.predict(
P_test
)
print(
y_pred
)
Inspect the learned coefficients
The fitted scikit-learn model is available at:
The learned coefficients are:
For example:
If there are three prediction columns, the shape is typically:
Inspect the intercept
With:
scikit-learn's fitted intercept is effectively zero.
With:
the model estimates an intercept from the training data.
Interpreting coefficients
Suppose the F-Step column order is:
and the learned coefficients are:
With no intercept, the learned aggregation rule is:
This example shows why the implementation should not be interpreted as a convex average.
KFC prediction-column order
The F-Step prediction matrix is ordered according to:
You can inspect the column names with:
Then pair them with the learned coefficients:
weights = model.cstep_.strategy_.model.coef_
for name, weight in zip(
divergence_names,
weights,
):
print(
name,
weight,
)
This is the recommended way to interpret which divergence-specific predictions receive positive or negative linear coefficients.
Using an intercept
To fit an intercept:
Then the model is:
Inspect:
and:
KFC integration
Conceptually, the registry name is:
and the intended KFC configuration is:
from kfc_procedure import KFCRegressor
model = KFCRegressor(
divergences=[
"euclidean",
"gkl",
],
local_model="ridge",
combiner="weighted_mean",
combiner_params={
"fit_intercept": False,
},
)
However, the current C-Step implementation introduces an important constructor compatibility issue.
Current C-Step random_state issue
When C-Step resolves a string combiner, the source does:
params = dict(
self.combiner_params
)
if "random_state" not in params:
params["random_state"] = self.random_state
and then:
That means a string-based weighted_mean configuration is constructed
conceptually as:
But the actual constructor is only:
It does not accept:
Result
The current string-based KFC path can raise:
This can happen even if the forwarded value is:
because the unsupported keyword is still present.
Current source limitation
With the current source version, weighted_mean is affected by C-Step's
unconditional random_state parameter injection.
This is an implementation issue in C-Step parameter forwarding, not an
issue with WeightedMeanCombiner itself.
Current workaround
Pass a pre-instantiated combiner object instead of the string name.
from kfc_procedure import KFCRegressor
from kfc_procedure.core.combiner.regression import (
WeightedMeanCombiner,
)
model = KFCRegressor(
divergences=[
"euclidean",
"gkl",
],
local_model="ridge",
combiner=WeightedMeanCombiner(
fit_intercept=False,
),
random_state=42,
)
C-Step checks:
so the already-created object is used directly and the unsupported
random_state keyword is not injected into its constructor.
How KFC trains weighted mean
KFC internally separates the provided training data into two subsets:
The K-Step and F-Step are fitted using:
Then:
is passed through those fitted stages to create:
The C-Step then fits:
Conceptually:
flowchart LR
DK["Dₖ"]
K["K-Step"]
F["F-Step"]
DL["Dₗ"]
P["Prediction matrix Pₗ"]
W["WeightedMeanCombiner"]
Y["Learned linear combiner"]
DK --> K --> F
DL --> P
F --> P
P --> W
DL --> W
W --> Y
The weighted combiner is therefore trained on divergence-level predictions generated for the internal aggregation subset.
Reconstruct the C-Step input
After fitting:
clusters = model.kstep_.predict(
X_test
)
P_test = model.fstep_.predict(
X_test,
clusters,
)
print(
P_test.shape
)
Then:
uses the fitted weighted linear model.
Inspect the fitted KFC combiner
After fitting:
Then:
should be:
when that strategy is used.
The internal linear regression model is:
Inspect KFC coefficients
Map the coefficients to divergence names:
divergence_names = list(
model.fstep_.models_.keys()
)
for name, weight in zip(
divergence_names,
weights,
):
print(
f"{name}: {weight}"
)
Inspect the KFC intercept
Its interpretation depends on:
used when creating the combiner.
Example coefficient table
import pandas as pd
divergence_names = list(
model.fstep_.models_.keys()
)
weights = model.cstep_.strategy_.model.coef_
table = pd.DataFrame({
"divergence": divergence_names,
"coefficient": weights,
})
print(
table
)
Conceptually:
Again, these are OLS coefficients, not normalized ensemble probabilities or convex weights.
Weighted mean vs ordinary mean
MeanCombiner uses:
Every column contributes equally.
WeightedMeanCombiner instead learns:
or:
So the coefficient assigned to each column is learned from data.
Weighted mean vs stacking regressor
Both use supervised C-Step fitting.
The default implementations differ as follows:
| Property | Weighted Mean | Stacking Regressor |
|---|---|---|
| Internal model | LinearRegression |
default LinearRegression |
| Default intercept | False |
sklearn default, normally True |
| Custom meta-model | No | Yes |
| Fitted estimator location | model |
meta_model_ |
| Clones configured estimator | No | Yes |
| Purpose | OLS coefficient combiner | general meta-regression abstraction |
With their defaults, both are linear, but they are not configured identically.
For more detail, see:
Weighted mean vs GradientCOBRA
WeightedMeanCombiner learns one global linear relationship:
GradientCOBRACombiner instead passes the F-Step matrix into a kernel-based
prediction-space aggregation procedure.
So their aggregation mechanisms are structurally different:
versus:
gradientcobra
prediction matrix
↓
distance / kernel consensus
↓
local weighted aggregation
↓
final value
For the algorithmic details, see:
Weighted mean with one divergence
If KFC uses only:
then the prediction matrix has shape:
Weighted mean still works as a linear calibration model:
or with an intercept:
So even with one divergence, the learned mapping can rescale or shift the single F-Step prediction column.
Weighted mean with several divergences
With:
the combiner learns four coefficients:
The source does not perform feature selection or regularization inside this combiner.
All behavior comes from standard LinearRegression.
Multicollinearity
Because WeightedMeanCombiner delegates directly to
sklearn.linear_model.LinearRegression, the source itself adds no special
handling for strongly correlated prediction columns.
For example, two divergences may produce very similar F-Step predictions.
The current combiner does not:
Any numerical behavior in such cases comes from the underlying
LinearRegression implementation.
No internal cross-validation
WeightedMeanCombiner does not perform cross-validation.
Its complete fitting logic is:
There is no internal search over:
If you need a more flexible learned C-Step, use a custom stacking meta-model or a COBRA strategy.
No normalization inside the combiner
The source does not normalize or standardize the prediction columns before calling:
The F-Step prediction matrix is used as supplied.
So if prediction columns have different numerical behavior, the current combiner itself does not rescale them first.
No explicit finite-value validation
WeightedMeanCombiner.fit() explicitly validates only:
It does not manually check:
Those errors, when relevant, are left to scikit-learn's LinearRegression.
A useful upstream check is:
Fit-before-predict behavior
The combiner itself does not maintain its own _is_fitted flag.
It relies on the wrapped scikit-learn model.
If:
is called before:
then LinearRegression.predict() is responsible for reporting that the model
is not fitted.
Direct use outside KFC
WeightedMeanCombiner can aggregate predictions from any regression models,
not only KFC divergence families.
For example:
P_train = np.column_stack([
pred_model_a_train,
pred_model_b_train,
pred_model_c_train,
])
P_test = np.column_stack([
pred_model_a_test,
pred_model_b_test,
pred_model_c_test,
])
combiner = WeightedMeanCombiner(
fit_intercept=False,
)
combiner.fit(
P_train,
y_train,
)
final_prediction = combiner.predict(
P_test
)
The class only assumes that X is a two-dimensional prediction matrix.
Custom constrained weighting
The current implementation does not provide constrained weights.
If you need:
and:
that behavior is not supported by WeightedMeanCombiner as currently written.
A custom BaseCombiner implementation would be needed for those constraints.
For example, the custom combiner would have to solve an explicitly constrained
optimization problem rather than using ordinary LinearRegression.
Debugging weighted mean
1. Inspect matrix shape
Expected:
2. Check finite values
3. Inspect fitted coefficients
4. Inspect intercept
5. Inspect divergence order
6. Check the current constructor error
If KFC raises:
while using:
use:
instead of the string path with the current source version.
Quick reference
| Property | Current behavior |
|---|---|
| Registry name | weighted_mean |
| Task | regression |
| Internal model | LinearRegression |
Default fit_intercept |
False |
Requires target in fit() |
Yes |
| Input requirement | 2D prediction matrix |
| Weight positivity enforced | No |
| Sum-to-one enforced | No |
| Internal normalization | No |
| Internal cross-validation | No |
| Regularization | No |
| Learned coefficients | model.coef_ |
| Learned intercept | model.intercept_ |
| Current string KFC path | affected by C-Step random_state injection |
Mental model
Weighted mean learns one global OLS coefficient for each F-Step prediction column.
Despite the class name, the current source implements unconstrained linear regression rather than a normalized weighted average.