Classification Combiners
Classification combiners are the C-Step strategies that turn the F-Step prediction matrix into one final class label.
The C-Step receives one prediction column per divergence:
where each entry is a class prediction produced by one divergence-specific local-model family.
Conceptually:
F-Step predictions
│
▼
┌──────────────────────────────┐
│ euclidean gkl logistic │
├──────────────────────────────┤
│ 0 0 1 │
│ 1 1 1 │
│ 2 1 2 │
└──────────────────────────────┘
│
▼
classification combiner
│
▼
final class label
The current source registers three classification combiners:
| Registry name | Implementation |
|---|---|
majority_vote |
MajorityVoteCombiner |
stacking_classifier |
StackingClassifierCombiner |
combined_classifier |
CobraClassifierCombiner |
Classification consensus families
-
Hard voting
majority_voteUses the most frequent class label in each prediction row.
-
Stacking
stacking_classifierFits a meta-classifier to the divergence prediction matrix.
-
Prediction-space consensus
combined_classifierWraps
CombinedClassifierand uses the F-Step matrix directly as prediction-space input.
majority_vote
MajorityVoteCombiner is registered as:
It performs row-wise hard voting.
For one sample:
the final output is:
Implementation
The current source uses:
for every row.
The output array is explicitly allocated as:
so the combiner itself can return non-numeric class labels.
Fit behavior
MajorityVoteCombiner is stateless.
Its fit() method is:
It does not learn from the target labels.
Input validation
The prediction matrix is converted with:
and must be two-dimensional.
If:
the combiner raises:
Direct example
import numpy as np
from kfc_procedure.core.combiner.classification import (
MajorityVoteCombiner,
)
P = np.array([
[0, 0, 1],
[1, 1, 1],
[2, 1, 2],
])
combiner = MajorityVoteCombiner()
combiner.fit(P)
prediction = combiner.predict(P)
print(prediction)
Conceptually:
Tie behavior
The current source does not implement a separate tie-breaking policy.
Tie behavior comes from:
and therefore depends on the order in which equally frequent labels are encountered.
Note
If deterministic domain-specific tie-breaking matters, implement a custom combiner rather than assuming a special KFC tie policy.
KFC example
Because of the current C-Step constructor issue, use a combiner instance:
from kfc_procedure import KFCClassifier
from kfc_procedure.core.combiner.classification import (
MajorityVoteCombiner,
)
model = KFCClassifier(
divergences=["euclidean"],
local_model="logistic_regression",
combiner=MajorityVoteCombiner(),
random_state=42,
)
model.fit(
X_train,
y_train,
)
y_pred = model.predict(
X_test
)
The reason for preferring an instance in the current source is explained in
Current random_state constructor issue.
stacking_classifier
StackingClassifierCombiner is registered as:
It learns a second-level classifier from the F-Step prediction matrix.
Conceptually:
Default meta-classifier
The constructor is:
If no meta-model is provided, it creates:
The current implementation stores this configuration object as:
Fit behavior
Before fitting, the source clones the configured meta-classifier:
Then it trains:
and marks:
Why clone?
The configured estimator and fitted estimator are kept separate:
This follows common scikit-learn fitted-state conventions.
Prediction
combine() first checks:
If the combiner has not been fitted, it raises:
Otherwise it delegates to:
Direct example
from kfc_procedure.core.combiner.classification import (
StackingClassifierCombiner,
)
combiner = StackingClassifierCombiner()
combiner.fit(
P_train,
y_train,
)
y_pred = combiner.predict(
P_test
)
Custom meta-classifier
Any compatible scikit-learn-style classifier can be supplied.
from sklearn.ensemble import RandomForestClassifier
from kfc_procedure.core.combiner.classification import (
StackingClassifierCombiner,
)
combiner = StackingClassifierCombiner(
meta_model=RandomForestClassifier(
n_estimators=200,
random_state=42,
)
)
After fitting:
returns the fitted clone.
KFC example
Use an instance with the current C-Step implementation:
from kfc_procedure import KFCClassifier
from kfc_procedure.core.combiner.classification import (
StackingClassifierCombiner,
)
model = KFCClassifier(
divergences=["euclidean"],
local_model="logistic_regression",
combiner=StackingClassifierCombiner(),
random_state=42,
)
Inspect fitted stacking state
After fitting:
Then:
For the default logistic-regression meta-classifier:
No combiner-level predict_proba()
The current StackingClassifierCombiner implements:
but does not implement:
So even if its underlying meta_model_ supports probability prediction,
CStep.predict_proba() does not expose that capability through this combiner.
combined_classifier
CobraClassifierCombiner is registered as:
It wraps the package's:
implementation.
Its constructor is:
and it creates:
KFC integration
The key fit call is:
Here X is the F-Step prediction matrix.
That means the wrapped CombinedClassifier does not fit another internal set
of base classifiers when used as the KFC C-Step.
Instead, the vector of divergence-level class predictions is treated directly as prediction-space data.
Prediction
The wrapper delegates:
Probability prediction
Unlike the other two current classification combiners,
CobraClassifierCombiner explicitly implements:
by calling:
So the C-Step strategy itself supports probability prediction when this combiner is used.
Example
from kfc_procedure import KFCClassifier
model = KFCClassifier(
divergences=["euclidean"],
local_model="logistic_regression",
combiner="combined_classifier",
combiner_params={
"distance": "hamming",
"kernel": "rbf",
"n_cv": 5,
},
random_state=42,
)
This string-based path is compatible with the current C-Step constructor logic
because CobraClassifierCombiner accepts arbitrary:
including the automatically forwarded random_state.
Inspect the wrapped CombinedClassifier
After fitting:
Depending on configuration and source version, useful fitted state can include attributes such as:
cobra.classes_
cobra.bandwidth_
cobra.pred_l_
cobra.distance_matrix_
cobra.global_majority_class_
cobra.optimization_outputs_
when those attributes are created by the wrapped estimator.
For the detailed algorithm, see:
Comparison
| Combiner | Learns from target? | Learned state | predict_proba() on combiner |
|---|---|---|---|
majority_vote |
No | none | No |
stacking_classifier |
Yes | meta_model_ |
No |
combined_classifier |
Yes | wrapped CombinedClassifier |
Yes |
Current random_state constructor issue
The current C-Step resolves a string combiner by copying:
and then injecting:
whenever the key is missing.
The relevant logic is:
params = dict(
self.combiner_params
)
if "random_state" not in params:
params["random_state"] = self.random_state
Then:
is called.
Affected classification combiners
These constructors do not accept random_state:
and:
Therefore a string-based configuration can result in calls equivalent to:
or:
and raise:
This can happen even when the forwarded value is None.
combined_classifier is not affected
Its wrapper constructor is:
so C-Step's injected random_state is accepted and forwarded to
CombinedClassifier.
Workaround for majority vote and stacking
Pass a combiner instance instead of its string name.
Majority vote
from kfc_procedure.core.combiner.classification import (
MajorityVoteCombiner,
)
model = KFCClassifier(
divergences=["euclidean"],
local_model="logistic_regression",
combiner=MajorityVoteCombiner(),
random_state=42,
)
Stacking
from kfc_procedure.core.combiner.classification import (
StackingClassifierCombiner,
)
model = KFCClassifier(
divergences=["euclidean"],
local_model="logistic_regression",
combiner=StackingClassifierCombiner(),
random_state=42,
)
C-Step handles a non-string object with:
so it does not reconstruct the object or inject constructor parameters.
Direct C-Step use
You can use classification combiners directly with a prediction matrix.
For example:
import numpy as np
P_train = np.array([
[0, 0, 1],
[1, 1, 1],
[0, 1, 0],
[1, 0, 1],
])
y_train = np.array([
0,
1,
0,
1,
])
Then:
and:
use the same interface employed by C-Step.
Prediction matrix shape
Classification combiners expect rows to be samples and columns to be prediction sources.
In KFC:
For example, with four divergences:
The source explicitly validates two-dimensional input in:
CobraClassifierCombiner delegates validation to the wrapped
CombinedClassifier.
One divergence
A classifier may use only one divergence:
Then the F-Step matrix has shape:
With majority vote, that single prediction is simply the most frequent value of a row containing one element, so the output matches that input label.
With stacking or CombinedClassifier, a learned mapping can still be fitted from the one-column prediction representation.
Several divergences
With:
the classification C-Step receives four labels per sample.
Example:
The selected combiner then decides how that prediction pattern becomes the final class.
C-Step task validation
For a string-based combiner, C-Step checks:
So the following is valid:
while a regression combiner such as:
is rejected for classification.
Inspect available classification combiners
from kfc_procedure.core.combiner import (
CombinerFactory,
)
print(
CombinerFactory.available_by_category(
"classification"
)
)
The current source should include:
subject to normal registry loading.
Inspect a fitted strategy
After KFC fitting:
Possible values are:
Reconstruct classification consensus manually
clusters = model.kstep_.predict(
X_test
)
P = model.fstep_.predict(
X_test,
clusters,
)
manual = model.cstep_.predict(
P
)
automatic = model.predict(
X_test
)
Then:
should normally return:
for the same fitted model and input.
Inspect prediction patterns
A useful debugging step is to pair the F-Step pattern with the final output.
divergence_names = list(
model.fstep_.models_.keys()
)
for i in range(
min(
10,
len(P),
)
):
print(
dict(
zip(
divergence_names,
P[i],
)
),
"->",
manual[i],
)
For majority vote, this shows the exact labels being counted.
For stacking or CombinedClassifier, it shows the feature vector seen by the learned consensus strategy.
Stacking with integer class predictions
The default stacking meta-model is logistic regression.
The F-Step matrix is therefore passed as a numeric feature matrix.
For example:
is treated as a vector of numeric predictor values.
The source does not one-hot encode divergence-level class labels before fitting the meta-classifier.
Note
This is an important implementation detail.
The current stacking combiner treats the prediction matrix entries directly as numeric features.
Arbitrary class-label caveat upstream
MajorityVoteCombiner itself allocates:
for its outputs, so it can represent string or object labels.
However, the current F-Step initializes each divergence prediction vector with:
which creates a floating-point array by default.
Therefore assigning string-valued local-classifier predictions into the F-Step buffer can fail before the C-Step receives them.
Current pipeline limitation
Numeric class labels are the safest choice with the current full KFC classification pipeline.
The combiner layer is more permissive than the current F-Step prediction buffer.
Probability support
CStep.predict_proba() first checks:
and then checks whether the fitted strategy has:
Among the current classification combiners:
End-to-end KFC probability limitation
Even though combined_classifier supports C-Step probabilities,
KFCClassifier.predict_proba() currently attempts:
but the current FStep does not define:
Therefore:
is not currently functional end to end.
This limitation occurs before the C-Step's combined_classifier
predict_proba() can be used in the top-level KFC path.
Calling CombinedClassifier probabilities directly
If you are working specifically with the fitted C-Step wrapper and already
have a suitable prediction matrix P, the strategy itself exposes:
for:
Example:
This bypasses the missing F-Step probability method because P has already
been constructed.
Learned combiners train on the aggregation subset
The KFC training flow separates the data internally:
So:
are trained on F-Step prediction patterns generated for the internal aggregation subset.
majority_vote is stateless and does not use y_l during its own fitting.
Majority vote vs learned consensus
The source provides different mechanisms rather than ranking them.
majority_vote
fixed row-wise mode
stacking_classifier
learned meta-classifier
combined_classifier
learned prediction-space consensus
Use whichever aggregation behavior you want to evaluate for the application.
Custom classification combiner
A custom strategy should subclass:
and implement:
For a string-registered combiner, include random_state compatibility with the
current C-Step.
Example:
import numpy as np
from collections import Counter
from kfc_procedure.core.combiner.base import (
BaseCombiner,
)
from kfc_procedure.core.combiner import (
CombinerFactory,
)
@CombinerFactory.register(
"first_mode",
categories={"classification"},
)
class FirstModeCombiner(
BaseCombiner
):
def __init__(
self,
random_state=None,
):
self.random_state = random_state
def fit(
self,
X,
y=None,
):
return self
def combine(
self,
X,
):
X = np.asarray(X)
output = np.empty(
len(X),
dtype=object,
)
for i, row in enumerate(X):
output[i] = Counter(
row
).most_common(1)[0][0]
return output
Custom probability combiner
A classification combiner can additionally implement:
For example:
class MyProbabilityCombiner(
BaseCombiner
):
def __init__(
self,
random_state=None,
):
self.random_state = random_state
def fit(
self,
X,
y,
):
...
return self
def combine(
self,
X,
):
...
def predict_proba(
self,
X,
):
...
Then CStep.predict_proba() can delegate to it when the task is
classification.
The full KFC path still needs F-Step probability support before top-level
KFCClassifier.predict_proba() can work.
Debugging classification consensus
1. Inspect the F-Step matrix
clusters = model.kstep_.predict(
X_test
)
P = model.fstep_.predict(
X_test,
clusters,
)
print(
P.shape
)
print(
P[:10]
)
2. Check divergence column order
3. Inspect the strategy
4. Inspect stacking meta-model
when using stacking.
5. Inspect CombinedClassifier
when using combined_classifier.
6. Check string-constructor errors
If the error is:
and the configured string is:
pass a pre-instantiated combiner object with the current source version.
Quick reference
| Name | Constructor | Stateless? | Probability support | Current string path |
|---|---|---|---|---|
majority_vote |
MajorityVoteCombiner() |
Yes | No | affected by random_state injection |
stacking_classifier |
StackingClassifierCombiner(meta_model=None) |
No | No | affected |
combined_classifier |
CobraClassifierCombiner(**cobra_params) |
No | Yes | compatible |
Mental model
Classification consensus receives several divergence-specific class decisions and turns the resulting prediction pattern into one final class.
The current source provides a fixed hard vote, a learned stacking classifier,
and a CombinedClassifier prediction-space wrapper.