pelinker.distillation¶
Held-out fidelity of the compact entity head against its HDBSCAN teacher.
Why this exists¶
Compact fit distills HDBSCAN into an MLP: the clusterer is fit on the clustering
subsample, approximate_predict extends labels to the full manifold, an MLP is
trained on those labels, and then the clusterer is discarded
(Linker.clusterer = None). Nothing about the student ever reached the fit report,
and the student was trained on 100 % of the teacher's labels — so even an in-sample
agreement number would have been circular.
This module holds out a slice before the head is trained and scores the student against the teacher on it.
Three things it measures that a naive agreement number hides¶
- Exact vs approximate teacher labels, separately. Rows inside the clustering
subsample carry HDBSCAN's own labels; every other row carries an
approximate_predictguess. Agreement against the second kind partly measures how well the student imitates the teacher's extrapolation errors. Pooling the two hides which is degrading. - Forced noise. The head is trained only on
cluster != -1(:func:~pelinker.entity_head._non_noise_xy), so it structurally cannot emit noise: every mention the teacher would have abstained on gets assigned some cluster. The abstain responsibility silently moves to the screeners andthr_score, so we report what fraction of teacher-noise rows get forced and with what confidence. - Score comparability. The teacher's score is HDBSCAN soft membership; the
student's is
max(predict_proba).thr_scoreis applied to both as if they were the same quantity. Emit rates at a reference threshold say whether that holds.
The holdout is grouped by pmid by default: mentions from one document are strongly
correlated, so a plain row shuffle leaks and reports an optimistic number.
DistillationFidelityMetrics
dataclass
¶
Held-out agreement between the compact entity head and its HDBSCAN teacher.
Source code in pelinker/distillation.py
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 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 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
emit_rate_threshold
instance-attribute
¶
Reference thr_score the two emit rates were measured at.
entity_agreement
instance-attribute
¶
Share of holdout rows where student and teacher resolve to the same entity id.
Computed over rows the teacher assigned to a real cluster; teacher-noise rows are
counted by :attr:noise_forced_fraction instead, since the student cannot emit noise
and would fail every one of them by construction.
entity_agreement_approx
instance-attribute
¶
Agreement on rows whose teacher label came from approximate_predict.
Materially below :attr:entity_agreement_exact means the student is chasing the
teacher's extrapolation rather than its clustering.
entity_agreement_exact
instance-attribute
¶
Agreement restricted to holdout rows carrying HDBSCAN's own labels.
noise_forced_fraction
instance-attribute
¶
Share of holdout rows the teacher called noise that the student assigns anyway.
Always 1.0 while the head trains on non-noise labels only; the useful signal is the
accompanying score distribution — if those scores sit below thr_score the abstain
survives, otherwise it was lost.
score_corr_vs_teacher
instance-attribute
¶
Pearson correlation of student and teacher scores; low means thr_score does
not mean the same thing on both paths.
emit_rate_relative_delta()
¶
|student - teacher| / teacher, or 0.0 when the teacher emits nothing.
Source code in pelinker/distillation.py
HoldoutSplit
dataclass
¶
Positional train/holdout indices into the full-manifold row order.
Source code in pelinker/distillation.py
n_groups
instance-attribute
¶
Distinct groups on the holdout side (equal to len(holdout_idx) for row).
apply_gates(metrics, config)
¶
Evaluate gates and warn (or raise) on failures per config.on_failure.
The default is to warn: a fit consumes an expensive corpus embedding, and discarding it mid-run over a quality bound is a worse outcome than shipping a model whose report says plainly that it failed.
Source code in pelinker/distillation.py
cluster_to_entity_map(entities, cluster_labels)
¶
Majority KB entity per emergent cluster (noise excluded).
Reuses :func:~pelinker.linker_cluster_training.cluster_composition_from_training_frame
so the student/teacher comparison resolves clusters exactly the way the fitted linker
does, rather than via a parallel majority-vote implementation.
Source code in pelinker/distillation.py
evaluate_distillation_fidelity(*, head, umap_holdout, teacher_labels, teacher_scores, exact_label_mask, cluster_entity_map, grouping, n_groups, emit_rate_threshold, noise_label)
¶
Score head against its teacher on a held-out slice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
umap_holdout
|
ndarray
|
Clustering-space coordinates of the holdout rows. |
required |
teacher_labels
|
ndarray
|
HDBSCAN cluster ids for those rows ( |
required |
teacher_scores
|
ndarray
|
HDBSCAN soft membership for those rows. |
required |
exact_label_mask
|
ndarray
|
True where |
required |
cluster_entity_map
|
dict[int, str]
|
Cluster → entity map built on the training rows only. |
required |
Source code in pelinker/distillation.py
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 | |
evaluate_gates(metrics, config)
¶
Check metrics against the configured bounds.
A gate whose input is undefined (e.g. no holdout rows the teacher scored) is treated as passing, with the reason recorded — an absent measurement is not evidence of failure, and failing the fit over it would be worse than saying so.
Source code in pelinker/distillation.py
grouped_holdout_split(frame, *, holdout_fraction, random_state, group_col=DEFAULT_GROUP_COLUMN)
¶
Split rows into train/holdout, keeping every group_col value on one side.
Falls back to a row-level split (recorded as grouping="row") when the column is
absent, or when grouping cannot produce a non-empty split — e.g. a corpus whose
mentions all share one pmid. The fallback is reported, never silent, because a
row-level split on correlated mentions yields an optimistic agreement number.
Source code in pelinker/distillation.py
labels_to_entities(cluster_labels, mapping, *, noise_label)
¶
Resolve cluster ids to entity ids; noise and unmapped ids become noise_label.