| """Named retrieval baselines and validation-only ensemble selection.""" |
|
|
| from __future__ import annotations |
|
|
| from collections.abc import Mapping, Sequence |
|
|
| import numpy as np |
| import pandas as pd |
| import torch |
| from torch.nn import functional as F |
|
|
|
|
| def pfam_jaccard_scores( |
| candidate_ids: Sequence[str], |
| reference_ids: Sequence[str], |
| pfam_sets: Mapping[str, set[str]], |
| aggregation: str = "max", |
| ) -> dict[str, float]: |
| if aggregation not in {"max", "mean"}: |
| raise ValueError("Pfam aggregation must be 'max' or 'mean'") |
| result: dict[str, float] = {} |
| references = [pfam_sets.get(identifier, set()) for identifier in reference_ids] |
| for candidate_id in candidate_ids: |
| candidate = pfam_sets.get(candidate_id, set()) |
| similarities = [] |
| for reference in references: |
| union = candidate | reference |
| similarities.append(len(candidate & reference) / len(union) if union else 0.0) |
| result[candidate_id] = float(max(similarities) if aggregation == "max" else np.mean(similarities)) |
| return result |
|
|
|
|
| def weighted_pfam_jaccard_scores( |
| candidate_ids: Sequence[str], |
| reference_ids: Sequence[str], |
| pfam_sets: Mapping[str, set[str]], |
| weights: Mapping[str, float], |
| unknown_weight: float = 1.0, |
| aggregation: str = "max", |
| ) -> dict[str, float]: |
| """Score BGCs with a learned weighted set Jaccard similarity.""" |
| if aggregation not in {"max", "mean"}: |
| raise ValueError("Weighted Pfam aggregation must be 'max' or 'mean'") |
|
|
| def value(token: str) -> float: |
| return max(0.0, float(weights.get(token, unknown_weight))) |
|
|
| references = [pfam_sets.get(identifier, set()) for identifier in reference_ids] |
| result: dict[str, float] = {} |
| for candidate_id in candidate_ids: |
| candidate = pfam_sets.get(candidate_id, set()) |
| similarities = [] |
| for reference in references: |
| union = candidate | reference |
| denominator = sum(value(token) for token in union) |
| numerator = sum(value(token) for token in candidate & reference) |
| similarities.append(numerator / denominator if denominator else 0.0) |
| result[candidate_id] = float( |
| max(similarities) if aggregation == "max" else np.mean(similarities) |
| ) |
| return result |
|
|
|
|
| def aggregate_raw_esm(gene_embeddings: torch.Tensor, aggregation: str = "mean") -> torch.Tensor: |
| if gene_embeddings.ndim != 2: |
| raise ValueError("Raw ESM aggregation expects [genes, dimension]") |
| if aggregation == "mean": |
| value = gene_embeddings.mean(dim=0) |
| elif aggregation == "max": |
| value = gene_embeddings.max(dim=0).values |
| else: |
| raise ValueError("Raw ESM aggregation must be 'mean' or 'max'") |
| return F.normalize(value, p=2, dim=0) |
|
|
|
|
| def cosine_scores( |
| candidate_ids: Sequence[str], |
| reference_ids: Sequence[str], |
| embeddings: Mapping[str, torch.Tensor], |
| aggregation: str = "mean", |
| ) -> dict[str, float]: |
| references = torch.stack([F.normalize(embeddings[item], dim=0) for item in reference_ids]) |
| candidates = torch.stack([F.normalize(embeddings[item], dim=0) for item in candidate_ids]) |
| similarities = candidates @ references.T |
| if aggregation == "mean": |
| values = similarities.mean(dim=1) |
| elif aggregation == "max": |
| values = similarities.max(dim=1).values |
| else: |
| raise ValueError("Cosine aggregation must be 'mean' or 'max'") |
| return {identifier: float(value) for identifier, value in zip(candidate_ids, values)} |
|
|
|
|
| def ensemble_scores( |
| cosine: Mapping[str, float], |
| jaccard: Mapping[str, float], |
| alpha: float, |
| ) -> dict[str, float]: |
| if not 0.0 <= alpha <= 1.0: |
| raise ValueError("Ensemble alpha must be in [0, 1]") |
| if set(cosine) != set(jaccard): |
| raise ValueError("Ensemble methods must score identical candidates") |
| return { |
| identifier: alpha * ((float(cosine[identifier]) + 1.0) / 2.0) |
| + (1.0 - alpha) * float(jaccard[identifier]) |
| for identifier in cosine |
| } |
|
|
|
|
| def select_ensemble_alpha( |
| validation_results: pd.DataFrame, |
| primary_metric: str, |
| ) -> float: |
| required = {"alpha", "group_id", primary_metric} |
| missing = required.difference(validation_results.columns) |
| if missing: |
| raise ValueError(f"Validation results missing columns: {sorted(missing)}") |
| means = validation_results.groupby("alpha")[primary_metric].mean() |
| best_value = means.max() |
| candidates = sorted(float(alpha) for alpha in means[means == best_value].index) |
| return min(candidates, key=lambda alpha: (abs(alpha - 0.5), alpha)) |
|
|