Skip to content

Commit f285ffa

Browse files
feat: make the FlexCNN multitask model the default (4.1.1)
The fused-trunk multitask model trained across 6,543 LC setups, bundled since 4.1.0 as an opt-in, is now what every core function and the command line load when no model is given. The 4.0 default stays bundled as `deeplc.core.LEGACY_MULTITASK_MODEL` so existing workflows can pin it. An uncalibrated `predict()` on a multitask model that carries setup names now reports the setup named by `DEFAULT_TASK_NAME` (`PXD005573_mcp`, the 200-minute gradient the DeepLC 1.x to 3.x models were trained on) rather than head 0, which for the new default was whichever setup sorted first. Calibration, fine-tuning and `return_matrix=True` are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fb487db commit f285ffa

9 files changed

Lines changed: 120 additions & 24 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to
77
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [4.1.1] - 2026-08-26
10+
11+
### Changed
12+
13+
- The default model is now the fused-trunk multitask model trained across 6,543 LC
14+
setups (`multitask_flexcnn_model.pt`, bundled since 4.1.0 as an opt-in). Every core
15+
function and the command line use it when no `model` is given. The 4.0 default,
16+
`multitask_model.pt`, stays bundled as `deeplc.core.LEGACY_MULTITASK_MODEL`; pass it
17+
as `model=` to reproduce 4.0 and 4.1.0 predictions exactly.
18+
- Uncalibrated `predict()` on a multitask model that carries setup names reports the
19+
setup named by `deeplc.core.DEFAULT_TASK_NAME` (`PXD005573_mcp`, the 200-minute
20+
gradient the DeepLC 1.x to 3.x models were trained on) instead of head 0, which for
21+
the new default was an arbitrary setup. `return_matrix=True` is unchanged, and so are
22+
calibration and fine-tuning, which select or fit the setup from the reference.
23+
924
## [4.1.0] - 2026-08-20
1025

1126
### Added

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
DeepLC predicts retention times for peptides carrying any modification. It does this by leveraging
1818
a deep learning model based on atomic composition features. Starting with v4, DeepLC comes with a
19-
multitask pretrained model covering multiple LC setups, enabling accurate predictions out of the
20-
box. For best results on a specific dataset, predictions can be calibrated or fine-tuned
19+
multitask pretrained model covering multiple LC setups (6,543 setups since v4.1.1), enabling
20+
accurate predictions out of the box. For best results on a specific dataset, predictions can be calibrated or fine-tuned
2121
using a small reference set of identified PSMs.
2222

2323
## Citation

‎deeplc/core.py‎

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,20 @@
2222
LOGGER = logging.getLogger(__name__)
2323

2424
DEEPLC_DIR = Path(__file__).resolve().parent
25-
DEFAULT_MODEL = DEEPLC_DIR / "package_data" / "models" / "multitask_model.pt"
25+
#: The model every core function uses when none is given: the fused-trunk multitask
26+
#: model trained across 6,543 LC setups (see :data:`FLEXCNN_MULTITASK_MODEL`).
27+
DEFAULT_MODEL = DEEPLC_DIR / "package_data" / "models" / "multitask_flexcnn_model.pt"
28+
29+
#: The 4.0 default, a shared-trunk model with one head per LC setup, kept so that
30+
#: existing workflows can pin it: ``predict(psms, model=LEGACY_MULTITASK_MODEL)``.
31+
LEGACY_MULTITASK_MODEL = DEEPLC_DIR / "package_data" / "models" / "multitask_model.pt"
32+
33+
#: The LC setup an uncalibrated ``predict()`` reports for a multitask model. The
34+
#: default model has 6,543 setups and no reference to choose between them, so the
35+
#: setup of the DeepLC 1.x to 3.x training data (PXD005573, 200-minute gradient) is
36+
#: used, which keeps uncalibrated output on the gradient earlier versions reported.
37+
#: Calibration and fine-tuning pick or fit the setup from the reference instead.
38+
DEFAULT_TASK_NAME = "PXD005573_mcp"
2639

2740
#: Below this many reference PSMs, fine-tuning measured worse than calibration on
2841
#: every held-out setup tried, so it is warned about rather than silently attempted.
@@ -40,10 +53,9 @@
4053
#: near 1 %.
4154
MAX_FINETUNE_ERROR_FRACTION = 0.15
4255

43-
#: Fused-trunk multitask model, trained across 6,543 LC setups. Not the default:
44-
#: switching would change every prediction, so the choice is left to the caller
45-
#: until the calibration path is adapted to its low-rank head.
46-
FLEXCNN_MULTITASK_MODEL = DEEPLC_DIR / "package_data" / "models" / "multitask_flexcnn_model.pt"
56+
#: Fused-trunk multitask model, trained across 6,543 LC setups. The default since
57+
#: 4.1.1; the name is kept for callers that pass it explicitly.
58+
FLEXCNN_MULTITASK_MODEL = DEFAULT_MODEL
4759

4860

4961
def predict(
@@ -65,8 +77,10 @@ def predict(
6577
Additional keyword arguments to pass to the prediction function.
6678
return_matrix
6779
If True, return the full prediction matrix of shape ``(n, n_heads)`` when using a
68-
multitask model. If False (default), return a 1D array of shape ``(n,)`` using
69-
head 0 when model output is 2D.
80+
multitask model. If False (default), return a 1D array of shape ``(n,)`` for the
81+
setup named by :data:`DEFAULT_TASK_NAME` when the model knows its setups, and head
82+
0 otherwise. Uncalibrated output is on that setup's gradient; use
83+
:func:`predict_and_calibrate` to map it onto your own.
7084
7185
Returns
7286
-------
@@ -96,7 +110,7 @@ def predict(
96110
and "task_idx" not in kwargs
97111
and _model_ops.supports_task_subset(loaded_model)
98112
):
99-
kwargs["task_idx"] = [0]
113+
kwargs["task_idx"] = [_default_task_idx(loaded_model)]
100114

101115
result = _model_ops.predict(
102116
model=loaded_model,
@@ -106,10 +120,24 @@ def predict(
106120
**kwargs,
107121
).numpy()
108122
if not return_matrix:
109-
return result[:, 0]
123+
return result[:, 0 if "task_idx" in kwargs else _default_task_idx(loaded_model)]
110124
return result
111125

112126

127+
def _default_task_idx(model: torch.nn.Module) -> int:
128+
"""
129+
Index of the setup an uncalibrated prediction reports.
130+
131+
:data:`DEFAULT_TASK_NAME` when the model carries setup names and lists it, else 0.
132+
A model without names, or a model of a single setup, has nothing to choose from.
133+
"""
134+
names = getattr(model, "task_names", None) or []
135+
try:
136+
return list(names).index(DEFAULT_TASK_NAME)
137+
except ValueError:
138+
return 0
139+
140+
113141
def calibrate(
114142
psm_list_reference: PSMList,
115143
model: torch.nn.Module | PathLike | str | None = None,

‎docs/source/migration.rst‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ Model checkpoints
6060
=================
6161

6262
Legacy ``.hdf5`` checkpoints from v3 are not compatible with v4. The bundled model
63-
has been retrained as a PyTorch multitask model (``multitask_model.pt``). Custom
64-
``.hdf5`` checkpoints cannot be loaded; retrain using the v4 API. The new model
65-
and should serve as an ideal starting point for fine-tuning to any custom setup.
63+
has been retrained as a PyTorch multitask model (``multitask_flexcnn_model.pt``
64+
since 4.1.1, covering 6,543 LC setups; ``multitask_model.pt`` in 4.0 and 4.1.0).
65+
Custom ``.hdf5`` checkpoints cannot be loaded; retrain using the v4 API. The new
66+
model should serve as an ideal starting point for fine-tuning to any custom setup.
6667

6768

6869
Backend: TensorFlow → PyTorch

‎docs/source/models.rst‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@ Prediction models
55
Default model
66
=============
77

8-
DeepLC 4.0 ships a pretrained multitask model (``multitask_model.pt``) as the
9-
default. This model was trained jointly across multiple LC setups and outputs
10-
one retention time prediction per setup. The best-fitting output head is
11-
selected automatically during calibration based on Pearson correlation to the
12-
observed retention times in the reference set.
8+
DeepLC ships a pretrained multitask model as the default. Since 4.1.1 this is
9+
``multitask_flexcnn_model.pt``: a fused-trunk convolutional model with a low-rank
10+
multitask head, trained jointly across 6,543 LC setups from public repositories.
11+
It outputs one retention time prediction (in minutes) per setup. The best-fitting
12+
setup is selected automatically during calibration based on Pearson correlation to
13+
the observed retention times in the reference set, and fine-tuning fits a new setup
14+
head (66 parameters) on the reference with the trunk frozen.
15+
16+
Without calibration, :func:`deeplc.predict` reports the setup named by
17+
:data:`deeplc.core.DEFAULT_TASK_NAME` (``PXD005573_mcp``, the 200-minute gradient
18+
that DeepLC 1.x to 3.x models were trained on), or the full matrix with
19+
``return_matrix=True``. The setup names are available as ``model.task_names``.
20+
21+
The 4.0 default, ``multitask_model.pt`` (shared trunk, one head per setup), stays
22+
bundled as :data:`deeplc.core.LEGACY_MULTITASK_MODEL` and can be passed as
23+
``model=`` to any core function to reproduce 4.0 and 4.1.0 predictions.
1324

1425
Training a model from scratch
1526
==============================

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "deeplc"
3-
version = "4.1.0"
3+
version = "4.1.1"
44
description = "DeepLC: Retention time prediction for (modified) peptides using Deep Learning."
55
readme = "README.md"
66
license = { file = "LICENSE" }

‎tests/test_core.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,45 @@ def test_predict_returns_matrix_when_flag_set():
7171
assert result.shape[1] > 1
7272

7373

74+
def test_default_model_is_the_flexcnn_multitask_model():
75+
"""Since 4.1.1 the fused-trunk model of 6,543 setups is what a bare call loads."""
76+
assert deeplc.core.DEFAULT_MODEL == deeplc.core.FLEXCNN_MULTITASK_MODEL
77+
assert deeplc.core.DEFAULT_MODEL.name == "multitask_flexcnn_model.pt"
78+
assert deeplc.core.LEGACY_MULTITASK_MODEL.name == "multitask_model.pt"
79+
assert deeplc.core.LEGACY_MULTITASK_MODEL.exists()
80+
81+
82+
def test_uncalibrated_predict_reports_the_default_setup():
83+
"""
84+
A bare ``predict`` returns the column of :data:`DEFAULT_TASK_NAME`, not head 0.
85+
86+
The default model lists thousands of setups, and head 0 is whichever sorted first.
87+
The PXD005573 setup keeps uncalibrated output on the gradient DeepLC 1.x to 3.x
88+
reported, so downstream code that never calibrated sees comparable numbers.
89+
"""
90+
model = deeplc.core._model_ops.load_model(deeplc.core.DEFAULT_MODEL, device="cpu")
91+
idx = list(model.task_names).index(deeplc.core.DEFAULT_TASK_NAME)
92+
assert idx != 0
93+
94+
psm_list = _make_psm_list(_PEPTIDES)
95+
single = deeplc.core.predict(psm_list, predict_kwargs={"device": "cpu"})
96+
matrix = deeplc.core.predict(psm_list, return_matrix=True, predict_kwargs={"device": "cpu"})
97+
np.testing.assert_allclose(single, matrix[:, idx], rtol=1e-5, atol=1e-4)
98+
assert not np.allclose(single, matrix[:, 0])
99+
assert np.isfinite(single).all()
100+
101+
102+
def test_legacy_multitask_model_still_loads_and_predicts():
103+
"""The 4.0 default remains bundled and usable when pinned explicitly."""
104+
result = deeplc.core.predict(
105+
_make_psm_list(_PEPTIDES),
106+
model=deeplc.core.LEGACY_MULTITASK_MODEL,
107+
predict_kwargs={"device": "cpu"},
108+
)
109+
assert result.shape == (len(_PEPTIDES),)
110+
assert np.isfinite(result).all()
111+
112+
74113
def test_predict_and_calibrate_auto_selects_reference():
75114
# 200 PSMs cycling through _PEPTIDES; 100 with qvalue<=0.01, 100 with qvalue=1.0.
76115
# auto-selection picks the 100 low-qvalue PSMs as reference.

‎tests/test_flexcnn.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,10 @@ def test_bundled_model_predicts_in_minutes():
549549
assert -100.0 < out.min() < 60.0
550550
assert 20.0 < out.max() < 1000.0
551551

552+
# Uncalibrated output reports the DeepLC 1.x to 3.x setup, not whichever sorted first.
553+
default_idx = list(model.task_names).index(core.DEFAULT_TASK_NAME)
552554
single = core.predict(peptides, model=path)
553-
np.testing.assert_allclose(single, out[:, 0], rtol=1e-5)
555+
np.testing.assert_allclose(single, out[:, default_idx], rtol=1e-5)
554556

555557

556558
def test_small_reference_set_warns_and_widens_validation(tmp_path, caplog):

‎tests/test_model_ops.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from torch.utils.data import Dataset
1010

1111
from deeplc._architecture import DeepLCModel
12-
from deeplc.core import DEFAULT_MODEL
12+
from deeplc.core import LEGACY_MULTITASK_MODEL
1313
from deeplc.data import split_datasets
1414

1515

@@ -59,15 +59,15 @@ def test_train_rejects_empty_validation_loader():
5959

6060

6161
@pytest.mark.skipif(
62-
not DEFAULT_MODEL.exists(),
62+
not LEGACY_MULTITASK_MODEL.exists(),
6363
reason="multitask model not bundled",
6464
)
6565
def test_load_multitask_model_without_prior_shim():
6666
"""multitask_model.pt must load even when the legacy module is not pre-registered."""
6767
# Remove any previously registered shim so the test is self-contained.
6868
sys.modules.pop("multitask_model", None)
6969

70-
model = _model_ops.load_model(DEFAULT_MODEL, device="cpu")
70+
model = _model_ops.load_model(LEGACY_MULTITASK_MODEL, device="cpu")
7171

7272
assert isinstance(model, DeepLCModel)
7373

0 commit comments

Comments
 (0)