Skill Tracer
Skills › Scientific Agent Skills › torchdrug › molecular_property_prediction.md

molecular_property_prediction.md

260 lines · skills/torchdrug/references/molecular_property_prediction.md at 5f2bc03

Open on GitHub
1# Molecular Property Prediction and Pretraining
2
3Follow the official
4[property prediction](https://torchdrug.ai/docs/tutorials/property_prediction.html)
5and
6[pretrained molecular representations](https://torchdrug.ai/docs/tutorials/pretrain.html)
7tutorials for TorchDrug 0.2.1.
8
9Run the [ClinTox cache preparation](datasets.md#clintox-download-repair) first;
10the packaged download URL is stale. Full benchmark/pretraining runs below are
11illustrative; the review uses small CPU checks, not converged pretrained models.

Molecular Property Prediction and Pretraining

Follow the official property prediction and pretrained molecular representations tutorials for TorchDrug 0.2.1.

Run the ClinTox cache preparation first; the packaged download URL is stale. Full benchmark/pretraining runs below are illustrative; the review uses small CPU checks, not converged pretrained models.

Raw lines 1 to 12
13## Supervised property prediction

Supervised property prediction

Raw lines 13 to 14
15### 1. Load and split data
17The official tutorial uses a random 80/10/10 ClinTox split:
19```python
20import torch
21from torchdrug import datasets
23dataset = datasets.ClinTox("~/molecule-datasets/")
24lengths = [int(0.8 * len(dataset)), int(0.1 * len(dataset))]
25lengths.append(len(dataset) - sum(lengths))
26train_set, valid_set, test_set = torch.utils.data.random_split(
27 dataset, lengths, generator=torch.Generator().manual_seed(1)
29```
31This is a random split, not a scaffold split. If a benchmark requires a scaffold
32split, use `data.scaffold_split(dataset, lengths)` and record the actual
33partition sizes/scaffold overlap. The 0.2.1 `ordered_scaffold_split` implementation
34hardcodes 80/10/10 despite accepting `lengths`; do not use it for custom ratios.

1. Load and split data

The official tutorial uses a random 80/10/10 ClinTox split:

import torch
from torchdrug import datasets

dataset = datasets.ClinTox("~/molecule-datasets/")
lengths = [int(0.8 * len(dataset)), int(0.1 * len(dataset))]
lengths.append(len(dataset) - sum(lengths))
train_set, valid_set, test_set = torch.utils.data.random_split(
    dataset, lengths, generator=torch.Generator().manual_seed(1)
)

This is a random split, not a scaffold split. If a benchmark requires a scaffold split, use data.scaffold_split(dataset, lengths) and record the actual partition sizes/scaffold overlap. The 0.2.1 ordered_scaffold_split implementation hardcodes 80/10/10 despite accepting lengths; do not use it for custom ratios.

Raw lines 15 to 35
36### 2. Define the representation model
38```python
39from torchdrug import models
41model = models.GIN(
42 input_dim=dataset.node_feature_dim,
43 hidden_dims=[256, 256, 256, 256],
44 short_cut=True,
45 batch_norm=True,
46 concat_hidden=True,
48```

2. Define the representation model

from torchdrug import models

model = models.GIN(
    input_dim=dataset.node_feature_dim,
    hidden_dims=[256, 256, 256, 256],
    short_cut=True,
    batch_norm=True,
    concat_hidden=True,
)
Raw lines 36 to 49
50### 3. Define the task
52```python
53from torchdrug import tasks
55task = tasks.PropertyPrediction(
56 model,
57 task=dataset.tasks,
58 criterion="bce",
59 metric=("auprc", "auroc"),
61```
63`task` means the target field name(s) or a mapping of target names to weights. It
64does not mean `"node"`, `"edge"`, or `"graph"`.
66Documented `PropertyPrediction` criteria are:
68- `"mse"`
69- `"bce"`
70- `"ce"`
72The API docstring lists these basic metrics:
74- `"mae"`
75- `"rmse"`
76- `"auprc"`
77- `"auroc"`
79Released source also supports `"acc"`, `"mcc"`, `"r2"`, `"spearmanr"`, and
80`"pearsonr"`. Use `"acc"`/`"mcc"` with CE multiclass outputs; the AUROC/AUPRC
81branches expect binary targets, not an implicit multiclass averaging scheme.
83Other useful constructor options include `num_mlp_layer`, `normalization`,
84`num_class`, `mlp_batch_norm`, `mlp_dropout`, and
85`graph_construction_model`.
87For large multi-label problems, inspect `tasks.MultipleBinaryClassification`,
88which has its own task IDs, metrics, and reweighting behavior.

3. Define the task

from torchdrug import tasks

task = tasks.PropertyPrediction(
    model,
    task=dataset.tasks,
    criterion="bce",
    metric=("auprc", "auroc"),
)

task means the target field name(s) or a mapping of target names to weights. It does not mean "node", "edge", or "graph".

Documented PropertyPrediction criteria are:

  • "mse"
  • "bce"
  • "ce"

The API docstring lists these basic metrics:

  • "mae"
  • "rmse"
  • "auprc"
  • "auroc"

Released source also supports "acc", "mcc", "r2", "spearmanr", and "pearsonr". Use "acc"/"mcc" with CE multiclass outputs; the AUROC/AUPRC branches expect binary targets, not an implicit multiclass averaging scheme.

Other useful constructor options include num_mlp_layer, normalization, num_class, mlp_batch_norm, mlp_dropout, and graph_construction_model.

For large multi-label problems, inspect tasks.MultipleBinaryClassification, which has its own task IDs, metrics, and reweighting behavior.

Raw lines 50 to 89
90### 4. Train with Engine
92```python
93from torchdrug import core
95optimizer = torch.optim.Adam(task.parameters(), lr=1e-3)
96solver = core.Engine(
97 task,
98 train_set,
99 valid_set,
100 test_set,
101 optimizer,
102 batch_size=1024,
104solver.train(num_epoch=100)
105solver.evaluate("valid")
106```
108Add `gpus=[0]` only for supported CUDA execution. Start with one epoch and a
109smaller batch for a smoke test.

4. Train with Engine

from torchdrug import core

optimizer = torch.optim.Adam(task.parameters(), lr=1e-3)
solver = core.Engine(
    task,
    train_set,
    valid_set,
    test_set,
    optimizer,
    batch_size=1024,
)
solver.train(num_epoch=100)
solver.evaluate("valid")

Add gpus=[0] only for supported CUDA execution. Start with one epoch and a smaller batch for a smoke test.

Raw lines 90 to 110
111## Manual prediction
113Use TorchDrug collation:
115```python
116from torchdrug import data
118batch = data.graph_collate([valid_set[i] for i in range(min(8, len(valid_set)))])
119task.eval()
120with torch.no_grad():
121 logits = task.predict(batch)
122 probabilities = torch.sigmoid(logits)
123 targets = task.target(batch)
124```
126For binary classification, `predict()` returns logits and the tutorial applies
127sigmoid. For normalized regression, TorchDrug 0.2.1 returns predictions on the
128original target scale; this changed from earlier releases.
130When predicting on CUDA manually, move the whole nested batch:
132```python
133from torchdrug import utils
135batch = utils.cuda(batch)
136```

Manual prediction

Use TorchDrug collation:

from torchdrug import data

batch = data.graph_collate([valid_set[i] for i in range(min(8, len(valid_set)))])
task.eval()
with torch.no_grad():
    logits = task.predict(batch)
    probabilities = torch.sigmoid(logits)
    targets = task.target(batch)

For binary classification, predict() returns logits and the tutorial applies sigmoid. For normalized regression, TorchDrug 0.2.1 returns predictions on the original target scale; this changed from earlier releases.

When predicting on CUDA manually, move the whole nested batch:

from torchdrug import utils

batch = utils.cuda(batch)
Raw lines 111 to 137
138## Self-supervised pretraining
140The tutorial uses ClinTox only as a small illustration and recommends a larger
141unlabeled corpus such as ZINC2m for real pretraining.
143Use matching pretraining features:
145```python
146dataset = datasets.ClinTox(
147 "~/molecule-datasets/",
148 atom_feature="pretrain",
149 bond_feature="pretrain",
151```

Self-supervised pretraining

The tutorial uses ClinTox only as a small illustration and recommends a larger unlabeled corpus such as ZINC2m for real pretraining.

Use matching pretraining features:

dataset = datasets.ClinTox(
    "~/molecule-datasets/",
    atom_feature="pretrain",
    bond_feature="pretrain",
)
Raw lines 138 to 152
153### InfoGraph
155```python
156from torchdrug import core, models, tasks
158gin_model = models.GIN(
159 input_dim=dataset.node_feature_dim,
160 hidden_dims=[300, 300, 300, 300, 300],
161 edge_input_dim=dataset.edge_feature_dim,
162 batch_norm=True,
163 readout="mean",
165model = models.InfoGraph(gin_model, separate_model=False)
166task = tasks.Unsupervised(model)
168optimizer = torch.optim.Adam(task.parameters(), lr=1e-3)
169solver = core.Engine(
170 task,
171 dataset,
172 None,
173 None,
174 optimizer,
175 batch_size=256,
177solver.train(num_epoch=100)
178solver.save("gin-infograph.pth")
179```

InfoGraph

from torchdrug import core, models, tasks

gin_model = models.GIN(
    input_dim=dataset.node_feature_dim,
    hidden_dims=[300, 300, 300, 300, 300],
    edge_input_dim=dataset.edge_feature_dim,
    batch_norm=True,
    readout="mean",
)
model = models.InfoGraph(gin_model, separate_model=False)
task = tasks.Unsupervised(model)

optimizer = torch.optim.Adam(task.parameters(), lr=1e-3)
solver = core.Engine(
    task,
    dataset,
    None,
    None,
    optimizer,
    batch_size=256,
)
solver.train(num_epoch=100)
solver.save("gin-infograph.pth")
Raw lines 153 to 180
181### Attribute masking
183```python
184model = models.GIN(
185 input_dim=dataset.node_feature_dim,
186 hidden_dims=[300, 300, 300, 300, 300],
187 edge_input_dim=dataset.edge_feature_dim,
188 batch_norm=True,
189 readout="mean",
191task = tasks.AttributeMasking(model, mask_rate=0.15)
193optimizer = torch.optim.Adam(task.parameters(), lr=1e-3)
194solver = core.Engine(
195 task,
196 dataset,
197 None,
198 None,
199 optimizer,
200 batch_size=256,
202solver.train(num_epoch=100)
203solver.save("gin-attribute-masking.pth")
204```

Attribute masking

model = models.GIN(
    input_dim=dataset.node_feature_dim,
    hidden_dims=[300, 300, 300, 300, 300],
    edge_input_dim=dataset.edge_feature_dim,
    batch_norm=True,
    readout="mean",
)
task = tasks.AttributeMasking(model, mask_rate=0.15)

optimizer = torch.optim.Adam(task.parameters(), lr=1e-3)
solver = core.Engine(
    task,
    dataset,
    None,
    None,
    optimizer,
    batch_size=256,
)
solver.train(num_epoch=100)
solver.save("gin-attribute-masking.pth")
Raw lines 181 to 205
206### Fine-tune the encoder
208Recreate the same GIN architecture and feature dimensions, then wrap it in the
209supervised task:
211```python
212model = models.GIN(
213 input_dim=dataset.node_feature_dim,
214 hidden_dims=[300, 300, 300, 300, 300],
215 edge_input_dim=dataset.edge_feature_dim,
216 batch_norm=True,
217 readout="mean",
219task = tasks.PropertyPrediction(
220 model,
221 task=dataset.tasks,
222 criterion="bce",
223 metric=("auprc", "auroc"),
226# Load only a checkpoint whose origin you trust (PyTorch 2.0 uses pickle).
227checkpoint = torch.load("gin-attribute-masking.pth", map_location="cpu")["model"]
228transfer = task.load_state_dict(checkpoint, strict=False)
229print(transfer.missing_keys, transfer.unexpected_keys)
230```
232Then construct a new optimizer and supervised `Engine`. `strict=False` is
233intentional because the pretraining and supervised task heads differ. Review
234missing and unexpected keys if changing the architecture.
235This direct transfer applies to **AttributeMasking**, where encoder keys start
236with `model.`. An InfoGraph checkpoint nests them under `model.model.`; blindly
237using `strict=False` can load zero encoder weights. Extract that encoder's keys
238and load the recreated GIN with `strict=True` before wrapping it in a new task.

Fine-tune the encoder

Recreate the same GIN architecture and feature dimensions, then wrap it in the supervised task:

model = models.GIN(
    input_dim=dataset.node_feature_dim,
    hidden_dims=[300, 300, 300, 300, 300],
    edge_input_dim=dataset.edge_feature_dim,
    batch_norm=True,
    readout="mean",
)
task = tasks.PropertyPrediction(
    model,
    task=dataset.tasks,
    criterion="bce",
    metric=("auprc", "auroc"),
)

# Load only a checkpoint whose origin you trust (PyTorch 2.0 uses pickle).
checkpoint = torch.load("gin-attribute-masking.pth", map_location="cpu")["model"]
transfer = task.load_state_dict(checkpoint, strict=False)
print(transfer.missing_keys, transfer.unexpected_keys)

Then construct a new optimizer and supervised Engine. strict=False is intentional because the pretraining and supervised task heads differ. Review missing and unexpected keys if changing the architecture. This direct transfer applies to AttributeMasking, where encoder keys start with model.. An InfoGraph checkpoint nests them under model.model.; blindly using strict=False can load zero encoder weights. Extract that encoder's keys and load the recreated GIN with strict=True before wrapping it in a new task.

Raw lines 206 to 239
240## Experiment checks
242- Confirm `dataset.tasks` names and label shapes.
243- Check labeled counts in each training target and both class counts for each
244 binary evaluation target. Empty/single-class metrics and zero/undefined
245 regression standard deviations are not meaningful performance estimates.
246- Confirm classification vs regression before choosing criterion and metrics.
247- Record the exact split protocol; do not mislabel random splits as scaffold
248 splits.
249- Use AUPRC as well as AUROC for heavily imbalanced binary tasks.
250- Keep feature arguments identical when loading pretrained weights.
251- Fit preprocessing only on the training split.
252- Reserve the test split until model selection is complete.

Experiment checks

  • Confirm dataset.tasks names and label shapes.
  • Check labeled counts in each training target and both class counts for each binary evaluation target. Empty/single-class metrics and zero/undefined regression standard deviations are not meaningful performance estimates.
  • Confirm classification vs regression before choosing criterion and metrics.
  • Record the exact split protocol; do not mislabel random splits as scaffold splits.
  • Use AUPRC as well as AUROC for heavily imbalanced binary tasks.
  • Keep feature arguments identical when loading pretrained weights.
  • Fit preprocessing only on the training split.
  • Reserve the test split until model selection is complete.
Raw lines 240 to 253
254## Source links
256- [Property tutorial](https://torchdrug.ai/docs/tutorials/property_prediction.html)
257- [Pretraining tutorial](https://torchdrug.ai/docs/tutorials/pretrain.html)
258- [Property task API](https://torchdrug.ai/docs/api/tasks.html#property-prediction-tasks)
259- [Molecule dataset API](https://torchdrug.ai/docs/api/datasets.html#molecule-property-prediction-datasets)
260- [0.2.1 release notes](https://github.com/DeepGraphLearning/torchdrug/releases/tag/v0.2.1)