feat: add composition-encoder multitask model - #113
Open
RobbinBouwmeester wants to merge 1 commit into
Open
Conversation
Adds an alternative multitask architecture and its trained weights, alongside the existing model rather than replacing it. Calibration is unchanged: the model returns one column per LC setup, so best-head selection and the existing 1-D calibrations work as they are. Architecture, in deeplc/_composition_architecture.py: * a kernel-1 pointwise stem decodes each position's six atom counts before any neighbour mixing, so the kernel-5 convolutions are not asked to disentangle residue identity and sequence context at once * a low-rank read-out: each LC setup is a 64-dimensional vector dotted with a projected trunk, so a new setup costs 66 parameters * matrix_sum is accepted for signature compatibility and ignored; it equals matrix.reshape(30, 2, 6).sum(1) exactly and carries no extra information The model requires the terminal-composition blocks in matrix_global and says so through requires_terminal_composition. DeepLCDataset gained a matching add_terminal_composition argument, and core.predict loads the model first so it can ask what encoding that model needs. load_model dispatches on the state dict and now accepts any nn.Module instance rather than DeepLCModel alone. Trained on 1,025 LC setups and 23.6M observations, with features from this branch's extractor. On a held-out 116,627-peptidoform split: 0.5464 min pooled MAE, 0.223 median absolute error, 0.479 per-dataset median MAE. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RobbinBouwmeester
changed the base branch from
feat/multitask-composition-model
to
main
August 12, 2026 17:08
RobbinBouwmeester
marked this pull request as ready for review
August 12, 2026 17:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #112, which this needs for the corrected features. Review that one first; this diff is against it.
What this adds
A second multitask architecture and its trained weights, alongside the existing model rather than replacing it.
DEFAULT_MODELis untouched.Calibration is unchanged. The model returns one column per LC setup, so best-head selection and the existing 1-D calibrations work exactly as they do today. I benchmarked replacing best-head selection with a ridge over head outputs and it was not worth it: across 20 datasets it improved median relative MAE from 2.49% to 2.31% at 1,000 calibration peptides, but it was a wash at 200 and worse on several runs. Not a good enough trade to change a working default.
Architecture
deeplc/_composition_architecture.py:matrix_sumis accepted and ignored. It equalsmatrix.reshape(30, 2, 6).sum(1)exactly, verified elementwise, so it carries no information the atom matrix lacks. Trained as the sole sequence input it was 2.8x worse.Plumbing
The model needs the terminal-composition blocks from #112 and declares this via
requires_terminal_composition.DeepLCDatasetgains a matchingadd_terminal_compositionargument.core.predictloads the model before building the dataset, so it can ask what encoding that model needs. The feature encoding is a property of the model, not of the call site.load_modeldispatches on the state dict (head.embeddingpresent) and now accepts anynn.Moduleinstance, notDeepLCModelalone. Architecture hyperparameters are inferred from tensor shapes, as the existing branch already does.Performance
Trained on 1,025 LC setups and 23.6M observations, features from #112's extractor. Held-out split of 116,627 peptidoforms, 2.36M observations:
2.2M parameters.
How to test
tests/test_composition_architecture.pycovers the forward shape, thatmatrix_sumgenuinely does not affect the output, that the packaged checkpoint round-trips throughload_modelandpredict, and that an N-terminal acetyl no longer predicts identically to the unmodified peptide.End to end through
deeplc.core.predicton 1,500 held-out peptidoforms: prediction matrix(1500, 1025), pooled MAE 0.514 min over 30,671 observations.Limits worth knowing
Unseen modifications work when the chemistry is close to training. On
datasets/prosit_ptm_2020.csv, never trained on, calibrated from peptides carrying only known chemistry: Methyl 2.1, Dimethyl 2.1, Succinyl 2.9, Malonyl 3.1, Formyl 3.4, Trimethyl 4.6, Propionyl 4.9, Crotonyl 6.2, Nitro 6.5 min, against a 2.8 min control.A modification introducing an element the corpus never contains will fail. Phosphorus enters this corpus only through phosphorylation; a model trained without phospho predicts phosphopeptides at 9.7 min. The same holds for an amino acid outside the training composition range: tryptophan held out entirely gives 14.1 min, and no encoding choice rescued it. Worth checking a novel modification's composition against the training distribution before trusting the prediction.
All results are single-seed; repeat runs of an identical configuration spread by about 0.045 min.
🤖 Generated with Claude Code