Base API¶
Core base classes and mixins for building polyview algorithms.
Overview¶
The polyview.base module provides a unified architecture for multi-view learning algorithms.
All polyview estimators inherit from one of the base classes, which handle:
Input validation and normalization of multi-view data
Sklearn compatibility (introspection, parameter validation, cloning)
Semantic contracts to signal algorithm intent to consumers
Common workflows (fit, transform, predict, fit_transform, fit_predict)
Why Multiple Base Classes?¶
The architecture uses multiple specialized base classes rather than a single monolithic base for several important reasons:
Semantic clarity: Each base class signals what the estimator does:
BaseFusion→ “This combines views into a single array”BaseMultiViewClusterer→ “This produces cluster labels”BaseMultiViewEmbedder→ “This produces a low-dimensional embedding”
This allows downstream code (like
PolyPipeline) to make intelligent decisions about visualization, mode transitions, and validation.Enforced contracts: Each base provides specific interface contracts:
BaseMultiViewTransformerrequirestransform()BaseMultiViewClustererrequireslabels_attribute after fittingBaseLateFusionrequirespredict()on per-view predictions
These contracts prevent accidental misuse and enable automated checks.
Extensibility: New specialized bases can be added without breaking existing code:
BaseFusionsupports both early and intermediate fusion paradigmsNew bases (e.g.,
BaseMultiViewRegressor) can be added as the library growsConsumers can check
isinstance(step, BaseFusion)for specific behavior
Composition via mixins: Mixins (
MultiViewTransformerMixin,MultiViewClusterMixin,MultiViewEmbedderMixin) separate concerns and can be mixed with different base classes as needed.
This design pattern, borrowed from sklearn and scikit-learn-compatible libraries, provides clarity and flexibility without overwhelming complexity.
Design Contract¶
Every polyview estimator follows these conventions:
__init__stores hyperparameters only — no data, no fitting.Every hyperparameter must be a keyword argument with a default.
Learned attributes are written as
attr_(trailing underscore), following sklearn convention, socheck_is_fittedworks.Views are always passed as
list[np.ndarray](or aMultiViewDataset), never as a single array.
Inheritance Hierarchy¶
The base classes form a hierarchy:
BaseMultiView (ABC)
├── BaseMultiViewTransformer (+ MultiViewTransformerMixin)
│ ├── BaseFusion
│ │ └── Used by: Fusion methods (ConcatFusion, KernelFusion, etc.)
│ └── Used by: Canonical correlation and other general transformers
├── BaseMultiViewClusterer (+ MultiViewClusterMixin)
│ └── Used by: Co-training, spectral clustering, NMF methods
└── BaseMultiViewEmbedder (+ MultiViewEmbedderMixin)
└── Used by: MultiViewMDS, and other embedding-specific algorithms
BaseEstimator (ABC)
└── BaseLateFusion
└── Used by: Late fusion voting and prediction aggregation
MultiViewDataset¶
Purpose: Unified container for multi-view datasets.
While all estimators accept lists of views (list[np.ndarray]), the MultiViewDataset class provides a richer container that:
Stores views as an ordered, immutable collection
Attaches metadata (view names, sample labels) to the data
Enables convenient access via indexing (e.g.,
mvd["audio"]) or iterationProvides utilities like
train_test_split()for common workflowsSimplifies pipelines by bundling data and metadata together
When to use:
Building complete multi-view datasets with labels and names
Organizing complex experiments with many views
Creating reproducible data loaders for benchmarking
Passing data through pipelines that track view identity
Example:
import numpy as np
from polyview import MultiViewDataset
# Create multi-view dataset with names and labels
X1 = np.random.rand(100, 10) # acoustic features
X2 = np.random.rand(100, 20) # visual features
y = np.random.randint(0, 3, 100) # class labels
mvd = MultiViewDataset(
[X1, X2],
labels=y,
view_names=["audio", "video"]
)
# Access by view name or index
print(mvd["audio"].shape) # (100, 10)
print(mvd.views[1].shape) # (100, 20)
print(mvd.n_views) # 2
print(mvd.n_samples) # 100
# Convenient data splitting
train, test = mvd.train_test_split(test_size=0.2, random_state=42)
Key attributes:
views— List of numpy arrays (one per view)labels— Sample labels (if provided)view_names— Human-readable names for each viewn_views— Number of viewsn_samples— Number of samples (shared across views)
Note: While MultiViewDataset provides convenience, all polyview estimators accept plain lists of arrays. Use MultiViewDataset when you need the additional metadata and utilities.
Base Classes & Mixins¶
BaseMultiView¶
Role: Foundation for all polyview algorithms.
Responsibilities:
Validates and normalizes list-of-views input
Stores view dimensions (
n_views_in_,n_features_in_,n_samples_)Supports sklearn utilities (
get_params(),set_params(),clone())Enforces view consistency across
fitandtransform
Key methods:
_validate_views(views, *, accept_sparse=False, reset=True)— Validates and coerces multi-view inputfit(views, y=None)— Abstract; subclasses must implement
Example:
class MyAlgorithm(BaseMultiView):
def fit(self, views, y=None):
self._validate_views(views)
self.is_fitted_ = True
return self
MultiViewTransformerMixin¶
Role: Signals that an algorithm produces a combined output from multiple views.
Responsibilities:
Enforces
transform(views)interfaceProvides default
fit_transform()that chainsfitandtransformOutput is typically a single array (fused representation)
Key methods:
transform(views)— Abstract; must returnndarray of shape (n_samples, n_components)fit_transform(views, y=None)— Default implementation callsfitthentransform
Used by:
polyview.fusion.early.ConcatFusion— concatenates views feature-wisepolyview.fusion.early.WeightedFusion— weighted sum of viewspolyview.fusion.early.NormalizedFusion— normalized view fusionpolyview.fusion.KernelFusion— kernel-based fusionpolyview.embed.GCCA— generalized canonical correlation analysispolyview.embed.MCCA— multiview canonical correlation analysis
MultiViewClusterMixin¶
Role: Signals that an algorithm produces cluster labels.
Responsibilities:
Enforces
fit()to storelabels_attributeProvides
fit_predict()convenience methodOutput is a 1-D integer label array
Key methods:
fit_predict(views, y=None)— Callsfitthen returnslabels_
Used by:
polyview.cluster.mv_kmeans.MultiViewKMeans— multi-view k-means clusteringpolyview.cluster.mv_coreg_sc.MultiViewCoRegSpectralClustering— co-regularized spectral clusteringpolyview.cluster.mv_cotrain_sc.MultiViewCoTrainSpectralClustering— co-training spectral clusteringpolyview.cluster.mv_nmf.MultiViewNMF— multi-view non-negative matrix factorization
MultiViewEmbedderMixin¶
Role: Signals that an algorithm produces a low-dimensional embedding.
Responsibilities:
Extends
MultiViewTransformerMixin(inheritstransformandfit_transform)Provides
embedding_property (with getter/setter) that stores the learned embeddingSemantically signals that output is an embedding, not just a feature transformation
Key properties:
embedding_— The learned low-dimensional representation (ndarray of shape (n_samples, n_components))
Used by:
polyview.embed.multiviewmds.MultiViewMDS— multi-view multidimensional scaling with adaptive weighting
Concrete Base Classes¶
BaseMultiViewTransformer¶
Role: Ready-to-subclass base for building multi-view transformation algorithms (both fusion and embedding preparation).
Combines:
BaseMultiView— Input validation, sklearn compatibilityMultiViewTransformerMixin—transform()andfit_transform()interface
Subclasses must implement:
fit(views, y=None)— Learn parameters from multi-view datatransform(views)— Apply transformation and return a single fused array
Subclassed by:
BaseFusion— For methods that combine views (early, intermediate, or other fusion paradigms)BaseMultiViewEmbedder— For methods that produce embeddings
Example:
class MyTransformer(BaseMultiViewTransformer):
def fit(self, views, y=None):
views = self._validate_views(views, reset=True)
# Learn from views
return self
def transform(self, views):
views = self._validate_views(views, reset=False)
# Combine views into single output
return np.concatenate(views, axis=1)
BaseFusion¶
Role: Ready-to-subclass base for fusion methods that combine views into a single fused representation.
Supports multiple fusion paradigms:
Early fusion: Combines views before any feature extraction
Intermediate fusion: Combines views after per-view processing (future capability)
Combines:
BaseMultiViewTransformer— Full transformer interface and multi-view handlingSemantic signal to consumers (e.g.,
PolyPipeline) that output is single-view
Subclasses must implement:
fit(views, y=None)— Learn fusion parameters from multi-view datatransform(views)— Fuse views and return a single array
Current implementations (early fusion):
polyview.fusion.early.ConcatFusion— horizontal concatenationpolyview.fusion.early.WeightedFusion— weighted concatenationpolyview.fusion.early.NormalizedFusion— normalized concatenationpolyview.fusion.KernelFusion— kernel-based fusion
Example:
class MyConcatFusion(BaseFusion):
def fit(self, views, y=None):
views = self._validate_views(views, reset=True)
self.n_features_out_ = sum(v.shape[1] for v in views)
return self
def transform(self, views):
views = self._validate_views(views, reset=False)
return np.concatenate(views, axis=1)
Note: All fusion methods are automatically recognized in PolyPipeline as producing single-view output, enabling correct diagram coloring and mode transitions.
BaseMultiViewClusterer¶
Role: Ready-to-subclass base for building multi-view clustering algorithms.
Combines:
BaseMultiView— Input validation, sklearn compatibilityMultiViewClusterMixin—fit_predict()convenience method
Subclasses must implement:
fit(views, y=None)— Learn clustering model, store labels inlabels_attribute
Example:
class MyClusterer(BaseMultiViewClusterer):
def __init__(self, n_clusters=2):
super().__init__()
self.n_clusters = n_clusters
def fit(self, views, y=None):
views = self._validate_views(views, reset=True)
# Clustering algorithm logic
self.labels_ = np.zeros(self.n_samples_, dtype=int)
return self
BaseMultiViewEmbedder¶
Role: Ready-to-subclass base for building multi-view embedding algorithms.
Combines:
BaseMultiView— Input validation, sklearn compatibilityMultiViewEmbedderMixin—transform()andembedding_property
Subclasses must implement:
fit(views, y=None)— Learn embedding parameters, store inembedding_attributetransform(views)— Return the embedding (typically non-parametric, returns storedembedding_)
Example:
class MyEmbedder(BaseMultiViewEmbedder):
def fit(self, views, y=None):
views = self._validate_views(views, reset=True)
# Compute embedding
self.embedding_ = np.random.rand(self.n_samples_, 2)
return self
def transform(self, views):
self._validate_views(views, reset=False)
return self.embedding_ # Return learned embedding
BaseLateFusion¶
Role: Abstract base for late-fusion aggregation methods that combine per-view predictions.
Inheritance:
Inherits from
BaseEstimatorandABCUses
@abstractmethoddecorator to enforce implementation (early detection of incomplete subclasses)
Contract:
Takes list of 1-D prediction vectors (one per view), not multi-view feature matrices
Returns a single aggregated 1-D prediction vector
Used in
polyview.pipeline.PolyPipelineto aggregate per-view classifier outputs
Abstract methods (subclasses must implement):
fit(preds_by_view, y=None)— Learn aggregation parameters from view-specific predictionspredict(preds_by_view)— Aggregate predictions and return combined output
Concrete methods:
fit_predict(preds_by_view, y=None)— Convenience method that callsfitthenpredict
Used by:
polyview.fusion.late.MajorityVote— aggregates predictions via majority votingCustom voting or averaging schemes in pipelines
Example:
class MyLateFusion(BaseLateFusion):
def fit(self, preds_by_view, y=None):
# Learn aggregation parameters
return self
def predict(self, preds_by_view):
# Average predictions across views
P = np.vstack([np.asarray(p, dtype=float) for p in preds_by_view])
return P.mean(axis=0)
Usage Patterns¶
Choosing the Right Base:
Goal |
Base Class |
|---|---|
Fuse views into single representation |
|
Generic multi-view transformation |
|
Learn low-dimensional embedding |
|
Perform multi-view clustering |
|
Aggregate per-view predictions |
|
API Reference¶
- class polyview.base.BaseFusion(*args: Any, **kwargs: Any)¶
Bases:
BaseMultiViewTransformerReady-to-subclass base for multi-view fusion methods.
Fusion methods combine multiple views into a single fused representation before downstream processing (clustering, classification, etc.).
This base is used for:
Early fusion: Combining views before any feature extraction
Intermediate fusion: Combining views after per-view processing
All fusion methods produce single-view output from multi-view input.
Semantically signals to consumers (e.g.,
PolyPipeline) that this estimator combines multiple input streams into one output stream.Subclasses must implement
fit()andtransform().Example
>>> class MyConcatFusion(BaseFusion): ... def fit(self, views, y=None): ... self._validate_views(views) ... return self ... def transform(self, views): ... validated = self._validate_views(views, reset=False) ... return np.concatenate(validated, axis=1)
- class polyview.base.BaseLateFusion(*args: Any, **kwargs: Any)¶
Bases:
BaseEstimator,ABCBase class for late-fusion estimators over per-view predictions.
Late-fusion estimators consume one 1-D prediction vector per view and return one fused 1-D prediction vector.
Example
>>> class MeanVote(BaseLateFusion): ... def fit(self, preds_by_view, y=None): ... return self ... def predict(self, preds_by_view): ... P = np.vstack([np.asarray(p, dtype=float) for p in preds_by_view]) ... return np.rint(P.mean(axis=0)).astype(int) >>> preds = [np.array([0, 1, 1]), np.array([1, 1, 0])] >>> MeanVote().fit_predict(preds) array([0, 1, 0])
- abstractmethod fit(preds_by_view: List[Iterable], y=None) BaseLateFusion¶
Fit the late-fusion model from per-view predictions.
- Parameters:
preds_by_view (list of array-like) – One 1-D prediction vector per view.
y (ignored)
- Return type:
self
- class polyview.base.BaseMultiView(*args: Any, **kwargs: Any)¶
Bases:
BaseEstimator,ABCBase class for all polyview estimators.
Inheriting from sklearn’s
BaseEstimatorgives every subclass: -get_params()/set_params()(introspection, Grid Search) -__repr__(human-readable) -clone()compatibility (cross-validation)Subclasses must implement
fit(). They should not overrideget_params/set_params— those are auto-generated from the__init__signature by sklearn.- Parameters:
n_views (int or None, default=None) – Expected number of views. When set,
_validate_views()raises if the data has a different count.Nonemeans “accept any number of views ≥ 1”.
Example
>>> class DummyMV(BaseMultiView): ... def fit(self, views, y=None): ... self._validate_views(views) ... self.is_fitted_ = True ... return self >>> X1 = np.random.rand(8, 3) >>> X2 = np.random.rand(8, 5) >>> DummyMV(n_views=2).fit([X1, X2]).is_fitted_ True
- abstractmethod fit(views: List, y=None) BaseMultiView¶
Fit the model from a list of views.
- Parameters:
views (list of array-like of shape (n_samples, n_features_i))
y (ignored for unsupervised methods)
- Return type:
self
- class polyview.base.BaseMultiViewClusterer(*args: Any, **kwargs: Any)¶
Bases:
BaseMultiView,MultiViewClusterMixinReady-to-subclass base for multi-view clustering algorithms.
Subclasses must implement
fit(). After fitting,labels_must be set as an attribute.Example
>>> class MyClusterer(BaseMultiViewClusterer): ... def __init__(self, n_clusters=2): ... super().__init__() ... self.n_clusters = n_clusters ... def fit(self, views, y=None): ... views = self._validate_views(views) ... # ... clustering logic ... ... self.labels_ = np.zeros(self.n_samples_, dtype=int) ... return self
- class polyview.base.BaseMultiViewEmbedder(*args: Any, **kwargs: Any)¶
Bases:
BaseMultiView,MultiViewEmbedderMixinReady-to-subclass base for multi-view embedding methods.
Subclasses must implement
fit()andtransform(). Store the embedding inself.embedding_after fitting.Example
>>> class IdentityEmbedder(BaseMultiViewEmbedder): ... def fit(self, views, y=None): ... validated = self._validate_views(views) ... self.embedding_ = validated[0] ... return self ... def transform(self, views): ... validated = self._validate_views(views, reset=False) ... return validated[0]
- class polyview.base.BaseMultiViewTransformer(*args: Any, **kwargs: Any)¶
Bases:
BaseMultiView,MultiViewTransformerMixinReady-to-subclass base for multi-view transformers.
Subclasses must implement
fit()andtransform().Example
>>> class MyConcatTransformer(BaseMultiViewTransformer): ... def fit(self, views, y=None): ... self._validate_views(views) ... return self ... def transform(self, views): ... validated = self._validate_views(views, reset=False) ... return np.concatenate(validated, axis=1)
- class polyview.base.MultiViewClusterMixin¶
Bases:
objectMixin for estimators that produce cluster labels.
Provides
fit_predictand enforces thatfitstoreslabels_as a fitted attribute.Example
>>> class ToyClusterer(BaseMultiView, MultiViewClusterMixin): ... def fit(self, views, y=None): ... self.labels_ = np.zeros(len(views[0]), dtype=int) ... return self >>> ToyClusterer().fit_predict([np.random.rand(4, 2)]) array([0, 0, 0, 0])
- class polyview.base.MultiViewEmbedderMixin¶
Bases:
MultiViewTransformerMixinMixin for estimators that produce a low-dimensional embedding.
Identical contract to
MultiViewTransformerMixinbut signals that the output is an embedding (not a feature transformation), so callers can make different downstream choices (e.g. skip PCA post-processing).Subclasses should store the final embedding in
embedding_.Example
>>> class ToyEmbedder(BaseMultiView, MultiViewEmbedderMixin): ... def fit(self, views, y=None): ... self.embedding_ = np.asarray(views[0]) ... return self ... def transform(self, views): ... return np.asarray(views[0]) >>> emb = ToyEmbedder().fit([np.random.rand(3, 2)]).embedding_ >>> emb.shape (3, 2)
- property embedding_: numpy.ndarray¶
- class polyview.base.MultiViewTransformerMixin¶
Bases:
objectMixin for estimators that implement
transform.Provides a default
fit_transformthat callsfitthentransform— subclasses only need to implement both methods.Example
>>> class AddOneTransformer(MultiViewTransformerMixin): ... def fit(self, views, y=None): ... return self ... def transform(self, views): ... return np.asarray(views[0]) + 1 >>> AddOneTransformer().fit_transform([np.array([[1.0], [2.0]])]).ravel() array([2., 3.])
- fit_transform(views: List, y=None) numpy.ndarray¶
Fit and immediately transform.
Equivalent to
self.fit(views, y).transform(views)but may be overridden for efficiency (e.g. CCA can reuse intermediate results).- Parameters:
views (list of array-like)
y (ignored)
- Return type:
ndarray