|
| 1 | +# dpa_tools |
| 2 | + |
| 3 | +Fine-tuning, descriptor extraction, cross-validation, and data utilities for |
| 4 | +DPA-3 pretrained models. Lives as a self-contained subpackage of `deepmd-kit` |
| 5 | +at `deepmd.dpa_tools`. |
| 6 | + |
| 7 | +## Relationship with deepmd-kit |
| 8 | + |
| 9 | +`dpa_tools` sits on top of deepmd-kit without modifying any existing module: |
| 10 | + |
| 11 | +- **Model loading**: `_backend.py` is the single choke point that imports |
| 12 | + `deepmd.pt.model.model.get_model` and `deepmd.pt.train.wrapper.ModelWrapper` |
| 13 | + to load DPA-3 checkpoints and extract descriptors. No other file in |
| 14 | + `dpa_tools` touches `deepmd.pt.*` directly. |
| 15 | +- **Training**: shells out to `dp --pt train` / `dp --pt freeze` / |
| 16 | + `dp --pt test`, auto-generating `input.json` config files. |
| 17 | +- **Inference**: deepmd-kit's built-in `DeepProperty` handles neural-network |
| 18 | + models; dpa_tools adds a lightweight frozen-descriptor + sklearn-head path. |
| 19 | +- **CLI**: registered as `dp dpa` subcommand group via `deepmd/main.py`. |
| 20 | + Torch and all DPA dependencies are loaded lazily — only when a `dp dpa ...` |
| 21 | + command actually runs. |
| 22 | +- **Lazy import**: `import deepmd.dpa_tools` does **not** trigger a `torch` |
| 23 | + import. `dp dpa --help` is equally lightweight. |
| 24 | + |
| 25 | +## Python API |
| 26 | + |
| 27 | +```python |
| 28 | +from deepmd.dpa_tools import ( |
| 29 | + DPAFineTuner, # train (frozen sklearn / finetune / linear probe) |
| 30 | + DPAPredictor, # read-only inference from frozen bundles |
| 31 | + MFTFineTuner, # multi-task fine-tuning |
| 32 | + DPATrainer, # single-task dp --pt train wrapper |
| 33 | + extract_descriptors, # standalone descriptor extraction |
| 34 | + cross_validate, # leak-proof cross-validation |
| 35 | + train_test_split, # formula-grouped data splitting |
| 36 | + # data tools |
| 37 | + convert, # structure file → deepmd/npy |
| 38 | + batch_convert, # glob-based batch conversion |
| 39 | + check_data, # data sanity checks |
| 40 | + attach_labels, # inject external label arrays |
| 41 | + load_dataset, # label-filtered data loading |
| 42 | +) |
| 43 | +``` |
| 44 | + |
| 45 | +### DPAFineTuner |
| 46 | + |
| 47 | +Four training strategies: |
| 48 | + |
| 49 | +| Strategy | Description | Best for | |
| 50 | +|----------|------------|----------| |
| 51 | +| `frozen_sklearn` | Freeze descriptor, extract once, fit sklearn head (RF/Ridge/MLP) | Small data (<1k samples), CPU inference | |
| 52 | +| `linear_probe` | Freeze backbone, train property fitting net only | Medium data, GPU | |
| 53 | +| `finetune` | Full-network fine-tuning | Larger data, GPU | |
| 54 | +| `scratch` | Train from random init (experimental) | Large-scale data only | |
| 55 | + |
| 56 | +```python |
| 57 | +model = DPAFineTuner( |
| 58 | + pretrained="/path/to/DPA-3.1-3M.pt", |
| 59 | + strategy="frozen_sklearn", |
| 60 | + predictor="rf", |
| 61 | + pooling="mean", |
| 62 | +) |
| 63 | +model.fit(train_data="/data/train", target_key="homo") |
| 64 | +model.predict("/data/test") |
| 65 | +model.freeze("model.dp-sklearn.pth") |
| 66 | +``` |
| 67 | + |
| 68 | +### DPAPredictor |
| 69 | + |
| 70 | +```python |
| 71 | +pred = DPAPredictor("model.dp-sklearn.pth") |
| 72 | +result = pred.predict("/data/test") # DotDict with .predictions |
| 73 | +metrics = pred.evaluate("/data/test") # DotDict with .mae, .rmse, .r2 |
| 74 | + |
| 75 | +# uncertainty: RF native, MLP via committee, Ridge raises |
| 76 | +result = pred.predict("/data/test", return_uncertainty=True) |
| 77 | +# → .predictions, .uncertainty |
| 78 | +``` |
| 79 | + |
| 80 | +### MFTFineTuner |
| 81 | + |
| 82 | +Joint downstream property head + auxiliary force-field head (arXiv:2601.08486): |
| 83 | + |
| 84 | +```python |
| 85 | +mft = MFTFineTuner( |
| 86 | + pretrained="/path/to/DPA-3.1-3M.pt", |
| 87 | + downstream_task_type="property", |
| 88 | + property_name="homo", |
| 89 | + aux_branch="MP_traj_v024_alldata_mixu", |
| 90 | +) |
| 91 | +mft.fit(train_data="/data/qm9", aux_data="/data/spice2") |
| 92 | +mft.evaluate("/data/qm9_test") |
| 93 | +``` |
| 94 | + |
| 95 | +### Descriptor extraction |
| 96 | + |
| 97 | +```python |
| 98 | +X = extract_descriptors( |
| 99 | + "/data/systems", |
| 100 | + pretrained="/path/to/DPA-3.1-3M.pt", |
| 101 | + pooling="mean+std", |
| 102 | +) |
| 103 | +# → np.ndarray (n_frames, feat_dim * 2) |
| 104 | +``` |
| 105 | + |
| 106 | +### Cross-validation |
| 107 | + |
| 108 | +Formula-grouped to prevent same-molecule leakage: |
| 109 | + |
| 110 | +```python |
| 111 | +from deepmd.dpa_tools import cross_validate, train_test_split |
| 112 | + |
| 113 | +systems = load_dataset("/data/root", label_key="energy") |
| 114 | +train, valid, test = train_test_split(systems, group_by="formula", seed=42) |
| 115 | + |
| 116 | +result = cross_validate(model, systems, label_key="energy", cv=5, group_by="formula") |
| 117 | +# → {"aggregate": {"mae_mean": ..., "rmse_std": ...}, ...} |
| 118 | +``` |
| 119 | + |
| 120 | +### Data tools |
| 121 | + |
| 122 | +```python |
| 123 | +convert("POSCAR", "output_dir", fmt="vasp/poscar", type_map=["Cu", "O"]) |
| 124 | +batch_convert("calcs/**/OUTCAR", "npy_root", fmt="vasp/outcar") |
| 125 | +check_data("/data/system") # → list[Issue] |
| 126 | +attach_labels(system, head="bandgap", values=np.array([1.0, 2.0, 3.0])) |
| 127 | +``` |
| 128 | + |
| 129 | +## CLI |
| 130 | + |
| 131 | +All commands live under `dp dpa` with two-level nesting: |
| 132 | + |
| 133 | +``` |
| 134 | +dp dpa |
| 135 | + extract-descriptors extract pooled DPA descriptors to .npy |
| 136 | + fit train a model (any strategy) |
| 137 | + mft multi-task fine-tuning |
| 138 | + cv cross-validate frozen_sklearn baseline |
| 139 | + predict predict with a frozen .pth bundle |
| 140 | + evaluate evaluate a frozen .pth against stored labels |
| 141 | + data |
| 142 | + convert structure file → deepmd/npy |
| 143 | + batch-convert glob-based batch conversion |
| 144 | + validate sanity-check deepmd/npy directories |
| 145 | + attach-labels inject .npy labels into a system |
| 146 | +``` |
| 147 | + |
| 148 | +`dp dpa --help` does not load torch. The parser is pure argparse in |
| 149 | +`deepmd/main.py`; the handler import happens lazily in |
| 150 | +`deepmd/entrypoints/main.py` only when `dp dpa ...` is invoked. |
| 151 | + |
| 152 | +```bash |
| 153 | +dp dpa fit \ |
| 154 | + --train-data /data/train \ |
| 155 | + --pretrained /path/to/DPA-3.1-3M.pt \ |
| 156 | + --strategy frozen_sklearn \ |
| 157 | + --predictor rf \ |
| 158 | + --target-key homo |
| 159 | + |
| 160 | +dp dpa extract-descriptors \ |
| 161 | + --data /data/sys1 /data/sys2 \ |
| 162 | + --pretrained /path/to/DPA-3.1-3M.pt \ |
| 163 | + --pooling mean+std \ |
| 164 | + --output features.npy |
| 165 | + |
| 166 | +dp dpa mft \ |
| 167 | + --data /data/qm9 \ |
| 168 | + --aux-data /data/spice2 \ |
| 169 | + --pretrained /path/to/DPA-3.1-3M.pt \ |
| 170 | + --property-name homo |
| 171 | + |
| 172 | +dp dpa data convert --input POSCAR --output npy_dir --fmt vasp/poscar |
| 173 | +dp dpa data validate --data /data/sys1 /data/sys2 |
| 174 | +``` |
| 175 | + |
| 176 | +## Installation |
| 177 | + |
| 178 | +```bash |
| 179 | +pip install deepmd-kit[dpa-tools] |
| 180 | +``` |
| 181 | + |
| 182 | +The `dpa-tools` extra brings in `scikit-learn`. `torch` and `dpdata` are |
| 183 | +already provided by deepmd-kit's core dependencies. |
| 184 | + |
| 185 | +## Internal architecture |
| 186 | + |
| 187 | +``` |
| 188 | +deepmd/dpa_tools/ |
| 189 | +├── __init__.py # public API, lazy imports (no torch at import time) |
| 190 | +├── _backend.py # single choke point for deepmd.pt.* calls |
| 191 | +├── cli.py # dp dpa subcommand handlers |
| 192 | +├── finetuner.py # DPAFineTuner (training + descriptor extraction) |
| 193 | +├── predictor.py # DPAPredictor (read-only inference + uncertainty) |
| 194 | +├── mft.py # MFTFineTuner (multi-task fine-tuning) |
| 195 | +├── trainer.py # DPATrainer (dp --pt train subprocess wrapper) |
| 196 | +├── cv.py # cross-validation + data splitting |
| 197 | +├── conditions.py # scalar condition manager (T, P) |
| 198 | +├── config/ |
| 199 | +│ └── manager.py # MFT input.json generation |
| 200 | +├── data/ |
| 201 | +│ ├── loader.py # polymorphic data loading |
| 202 | +│ ├── dataset.py # label-filtered loading |
| 203 | +│ ├── convert.py # format conversion |
| 204 | +│ ├── validate.py # data sanity checks |
| 205 | +│ ├── desc_cache.py # two-tier descriptor cache |
| 206 | +│ ├── type_map.py # automatic type-map resolution |
| 207 | +│ └── errors.py # DPADataError |
| 208 | +└── utils/ |
| 209 | + ├── dotdict.py # DotDict |
| 210 | + └── sklearn_heads.py # sklearn regressor factory |
| 211 | +``` |
| 212 | + |
| 213 | +Key design points: |
| 214 | +- `_backend.py` is the **only** file that imports `deepmd.pt.*` — every call |
| 215 | + into deepmd internals goes through it |
| 216 | +- `_DescriptorExtraction` encapsulates the fragile chain |
| 217 | + `wrapper.model["Default"]` → `set_eval_descriptor_hook` → `forward_common` |
| 218 | + → `eval_descriptor()` |
| 219 | +- `dp --pt train/test/freeze` always runs as a subprocess, keeping |
| 220 | + dpa_tools decoupled from deepmd-kit's training entry points |
| 221 | +- `dpdata.System` is the universal internal data format |
0 commit comments