Skip to content

Unlabeled anchor alignment

neureptrace.decoding.unlabeled_anchor_alignment implements a clean Category 2 / unlabeled target-adaptive alignment protocol for shared calibration anchors.

Use this when source subjects and the held-out target subject share a label-free calibration axis, for example:

  • movie time bins,
  • stimulus identifiers,
  • event identifiers,
  • resting-state segment ids,
  • other external anchors that are not the decoded target labels.

The target projection is fitted from target calibration features and their anchor ids, then applied to separate target test rows. The API intentionally has no target_labels argument. Passing decoded class labels as anchors would no longer be an ordinary Category-2 claim.

neureptrace.decoding.unlabeled_anchor_alignment

Unlabeled anchor calibration for Category-2 cross-subject alignment.

This module implements the clean deployment version of hyperalignment-like target calibration: source subjects and the held-out target subject share an external, label-free calibration axis such as movie time points, stimulus identifiers, or resting-state segment ids. Source and target projections are fitted from those anchors, a source-label classifier can then be trained in the shared latent space, and target test rows are transformed without using target class labels.

The public API intentionally has no target_labels argument. Passing decoded class labels as anchors would violate the intended Category-2 interpretation and should be reported as supervised calibration outside this module.

AnchorProjection dataclass

Linear projection from one subject/domain into an anchor template.

Source code in src/neureptrace/decoding/unlabeled_anchor_alignment.py
30
31
32
33
34
35
36
37
38
39
@dataclass(frozen=True, slots=True)
class AnchorProjection:
    """Linear projection from one subject/domain into an anchor template."""

    domain_id: Hashable
    feature_mean: np.ndarray
    template_mean: np.ndarray
    projection: np.ndarray
    n_anchor_rows: int
    regularization: float

UnlabeledAnchorAlignmentResult dataclass

Aligned source and target features plus Category-2 provenance.

Source code in src/neureptrace/decoding/unlabeled_anchor_alignment.py
42
43
44
45
46
47
48
49
50
51
52
@dataclass(frozen=True, slots=True)
class UnlabeledAnchorAlignmentResult:
    """Aligned source and target features plus Category-2 provenance."""

    train_features: np.ndarray
    test_features: np.ndarray
    source_projections: Mapping[Hashable, AnchorProjection]
    target_projection: AnchorProjection
    common_anchors: tuple[Hashable, ...]
    template: np.ndarray
    metadata: dict[str, Any] = field(default_factory=dict)

fit_unlabeled_anchor_alignment(*, source_features, source_domains, source_anchor_values, target_calibration_features, target_calibration_anchor_values, target_test_features, n_components=DEFAULT_UNLABELED_ANCHOR_COMPONENTS, regularization=DEFAULT_UNLABELED_ANCHOR_REGULARIZATION, min_common_anchors=2)

Fit source and target projections from shared unlabeled anchors.

Parameters:

Name Type Description Default
source_features Sequence[Sequence[float]] | ndarray

Source feature rows pooled across source subjects/domains.

required
source_domains Sequence[Hashable] | ndarray

One source-domain identifier per source row, typically subject id.

required
source_anchor_values Sequence[Hashable] | ndarray

One label-free calibration anchor per source row. Anchors may be movie time-bin ids, stimulus ids, event ids, or other external alignment keys. They must not be held-out target class labels if the result is reported as Category 2.

required
target_calibration_features Sequence[Sequence[float]] | ndarray

Held-out target-subject calibration rows used only for fitting the target projection. These rows should be disjoint from scored target test rows when the benchmark is not explicitly transductive.

required
target_calibration_anchor_values Sequence[Hashable] | ndarray

Label-free target calibration anchors in the same anchor space as source_anchor_values.

required
target_test_features Sequence[Sequence[float]] | ndarray

Held-out target-subject rows to transform into the shared anchor space.

required
n_components int | str | float

Maximum latent dimension. The effective dimension is capped by the number of common anchors minus one and the feature dimension.

DEFAULT_UNLABELED_ANCHOR_COMPONENTS
regularization float | str

Ridge regularization for each subject/domain projection.

DEFAULT_UNLABELED_ANCHOR_REGULARIZATION
min_common_anchors int | str

Minimum number of anchors that must be present in every source domain and in the target calibration set.

2

Returns:

Type Description
UnlabeledAnchorAlignmentResult

Source train rows and target test rows in a shared anchor-template space.

Notes

This is a Category-2 protocol. It uses source features, source domain ids, source anchor values, target calibration features, and target calibration anchor values. It never accepts target class labels.

Source code in src/neureptrace/decoding/unlabeled_anchor_alignment.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def fit_unlabeled_anchor_alignment(
    *,
    source_features: Sequence[Sequence[float]] | np.ndarray,
    source_domains: Sequence[Hashable] | np.ndarray,
    source_anchor_values: Sequence[Hashable] | np.ndarray,
    target_calibration_features: Sequence[Sequence[float]] | np.ndarray,
    target_calibration_anchor_values: Sequence[Hashable] | np.ndarray,
    target_test_features: Sequence[Sequence[float]] | np.ndarray,
    n_components: int | str | float = DEFAULT_UNLABELED_ANCHOR_COMPONENTS,
    regularization: float | str = DEFAULT_UNLABELED_ANCHOR_REGULARIZATION,
    min_common_anchors: int | str = 2,
) -> UnlabeledAnchorAlignmentResult:
    """Fit source and target projections from shared unlabeled anchors.

    Parameters
    ----------
    source_features:
        Source feature rows pooled across source subjects/domains.
    source_domains:
        One source-domain identifier per source row, typically subject id.
    source_anchor_values:
        One label-free calibration anchor per source row.  Anchors may be movie
        time-bin ids, stimulus ids, event ids, or other external alignment keys.
        They must not be held-out target class labels if the result is reported as
        Category 2.
    target_calibration_features:
        Held-out target-subject calibration rows used only for fitting the target
        projection.  These rows should be disjoint from scored target test rows
        when the benchmark is not explicitly transductive.
    target_calibration_anchor_values:
        Label-free target calibration anchors in the same anchor space as
        ``source_anchor_values``.
    target_test_features:
        Held-out target-subject rows to transform into the shared anchor space.
    n_components:
        Maximum latent dimension.  The effective dimension is capped by the number
        of common anchors minus one and the feature dimension.
    regularization:
        Ridge regularization for each subject/domain projection.
    min_common_anchors:
        Minimum number of anchors that must be present in every source domain and
        in the target calibration set.

    Returns
    -------
    UnlabeledAnchorAlignmentResult
        Source train rows and target test rows in a shared anchor-template space.

    Notes
    -----
    This is a Category-2 protocol.  It uses source features, source domain ids,
    source anchor values, target calibration features, and target calibration
    anchor values.  It never accepts target class labels.
    """

    source_matrix = _feature_matrix(source_features, name="source_features")
    target_calibration_matrix = _feature_matrix(target_calibration_features, name="target_calibration_features")
    target_test_matrix = _feature_matrix(target_test_features, name="target_test_features")
    if target_calibration_matrix.shape[1] != source_matrix.shape[1]:
        raise ValueError(
            "target_calibration_features and source_features must have the same feature width: "
            f"{target_calibration_matrix.shape[1]} != {source_matrix.shape[1]}."
        )
    if target_test_matrix.shape[1] != source_matrix.shape[1]:
        raise ValueError(
            "target_test_features and source_features must have the same feature width: "
            f"{target_test_matrix.shape[1]} != {source_matrix.shape[1]}."
        )

    source_domain_vector = _hashable_vector(source_domains, expected_length=source_matrix.shape[0], name="source_domains")
    source_anchor_vector = _hashable_vector(source_anchor_values, expected_length=source_matrix.shape[0], name="source_anchor_values", allow_missing=True)
    target_anchor_vector = _hashable_vector(
        target_calibration_anchor_values,
        expected_length=target_calibration_matrix.shape[0],
        name="target_calibration_anchor_values",
        allow_missing=True,
    )
    reg = _normalize_nonnegative_float(regularization, name="regularization")
    min_anchors = _normalize_positive_int(min_common_anchors, name="min_common_anchors")

    source_domains_ordered = tuple(dict.fromkeys(source_domain_vector.tolist()))
    if len(source_domains_ordered) < 1:
        raise ValueError("At least one source domain is required.")
    source_anchor_means = {
        domain: _anchor_means(source_matrix[source_domain_vector == domain], source_anchor_vector[source_domain_vector == domain])
        for domain in source_domains_ordered
    }
    target_anchor_means = _anchor_means(target_calibration_matrix, target_anchor_vector)
    common_anchors = _common_anchor_order(source_anchor_means, target_anchor_means, source_anchor_vector, target_anchor_vector)
    if len(common_anchors) < min_anchors:
        raise ValueError(
            "unlabeled anchor alignment requires at least "
            f"{min_anchors} common anchors across all source domains and target calibration; got {len(common_anchors)}."
        )

    source_anchor_matrices = {
        domain: _matrix_for_anchors(anchor_means, common_anchors, name=f"source domain {domain!r}")
        for domain, anchor_means in source_anchor_means.items()
    }
    target_anchor_matrix = _matrix_for_anchors(target_anchor_means, common_anchors, name="target calibration")
    template = anchor_template(len(common_anchors), n_components=n_components, feature_dim=source_matrix.shape[1])

    source_projections = {
        domain: fit_anchor_projection(anchor_matrix, template, domain_id=domain, regularization=reg)
        for domain, anchor_matrix in source_anchor_matrices.items()
    }
    target_projection = fit_anchor_projection(target_anchor_matrix, template, domain_id="target", regularization=reg)

    train_aligned = np.empty((source_matrix.shape[0], template.shape[1]), dtype=float)
    for domain, projection in source_projections.items():
        mask = source_domain_vector == domain
        train_aligned[mask] = transform_with_anchor_projection(source_matrix[mask], projection)
    test_aligned = transform_with_anchor_projection(target_test_matrix, target_projection)

    metadata = _metadata(
        n_source_rows=source_matrix.shape[0],
        n_target_calibration_rows=target_calibration_matrix.shape[0],
        n_target_test_rows=target_test_matrix.shape[0],
        feature_dim=source_matrix.shape[1],
        latent_dim=template.shape[1],
        n_source_domains=len(source_domains_ordered),
        n_common_anchors=len(common_anchors),
        common_anchors=common_anchors,
        regularization=reg,
        requested_components=n_components,
        min_common_anchors=min_anchors,
    )
    return UnlabeledAnchorAlignmentResult(
        train_features=train_aligned.astype(np.float32, copy=False),
        test_features=test_aligned.astype(np.float32, copy=False),
        source_projections=source_projections,
        target_projection=target_projection,
        common_anchors=common_anchors,
        template=template.astype(np.float32, copy=False),
        metadata=metadata,
    )

anchor_template(n_anchor_rows, *, n_components=DEFAULT_UNLABELED_ANCHOR_COMPONENTS, feature_dim=None)

Return a centered simplex template for shared unlabeled anchors.

Source code in src/neureptrace/decoding/unlabeled_anchor_alignment.py
195
196
197
198
199
200
201
202
203
204
205
206
207
def anchor_template(n_anchor_rows: int | str, *, n_components: int | str | float = DEFAULT_UNLABELED_ANCHOR_COMPONENTS, feature_dim: int | str | None = None) -> np.ndarray:
    """Return a centered simplex template for shared unlabeled anchors."""

    n_rows = _normalize_positive_int(n_anchor_rows, name="n_anchor_rows")
    if n_rows < 2:
        raise ValueError("At least two anchor rows are required to build an anchor template.")
    feature_cap = n_rows if feature_dim is None else _normalize_positive_int(feature_dim, name="feature_dim")
    actual = _effective_components(n_components, n_anchor_rows=n_rows, feature_dim=feature_cap)
    centered_identity = np.eye(n_rows, dtype=float) - np.full((n_rows, n_rows), 1.0 / n_rows)
    u, singular_values, _vt = np.linalg.svd(centered_identity, full_matrices=False)
    template = u[:, :actual] * singular_values[:actual]
    template -= np.mean(template, axis=0, keepdims=True)
    return template

fit_anchor_projection(anchor_features, template, *, domain_id='domain', regularization=DEFAULT_UNLABELED_ANCHOR_REGULARIZATION)

Fit a ridge projection from one domain's anchor rows to a template.

Source code in src/neureptrace/decoding/unlabeled_anchor_alignment.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def fit_anchor_projection(
    anchor_features: Sequence[Sequence[float]] | np.ndarray,
    template: Sequence[Sequence[float]] | np.ndarray,
    *,
    domain_id: Hashable = "domain",
    regularization: float | str = DEFAULT_UNLABELED_ANCHOR_REGULARIZATION,
) -> AnchorProjection:
    """Fit a ridge projection from one domain's anchor rows to a template."""

    matrix = _feature_matrix(anchor_features, name="anchor_features")
    template_matrix = _feature_matrix(template, name="template")
    if matrix.shape[0] != template_matrix.shape[0]:
        raise ValueError(f"anchor_features and template must have the same row count: {matrix.shape[0]} != {template_matrix.shape[0]}.")
    reg = _normalize_nonnegative_float(regularization, name="regularization")
    try:
        hash(domain_id)
    except TypeError as exc:
        raise ValueError(f"domain_id must be hashable, got {domain_id!r}.") from exc

    feature_mean = np.mean(matrix, axis=0)
    template_mean = np.mean(template_matrix, axis=0)
    centered_features = matrix - feature_mean
    centered_template = template_matrix - template_mean
    gram = centered_features.T @ centered_features
    scale = float(np.trace(gram) / max(1, gram.shape[0]))
    ridge = reg * max(scale, 1.0)
    system = gram + ridge * np.eye(gram.shape[0], dtype=float)
    rhs = centered_features.T @ centered_template
    try:
        projection = np.linalg.solve(system, rhs)
    except np.linalg.LinAlgError:
        projection = np.linalg.pinv(system) @ rhs
    return AnchorProjection(
        domain_id=domain_id,
        feature_mean=feature_mean,
        template_mean=template_mean,
        projection=projection,
        n_anchor_rows=matrix.shape[0],
        regularization=reg,
    )

transform_with_anchor_projection(features, projection)

Transform rows with a fitted anchor projection.

Source code in src/neureptrace/decoding/unlabeled_anchor_alignment.py
252
253
254
255
256
257
258
259
260
261
def transform_with_anchor_projection(features: Sequence[Sequence[float]] | np.ndarray, projection: AnchorProjection) -> np.ndarray:
    """Transform rows with a fitted anchor projection."""

    matrix = _feature_matrix(features, name="features")
    if matrix.shape[1] != projection.projection.shape[0]:
        raise ValueError(
            "features column count does not match the anchor projection: "
            f"{matrix.shape[1]} != {projection.projection.shape[0]}."
        )
    return (matrix - projection.feature_mean) @ projection.projection + projection.template_mean