D4CMPP2 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- D4CMPP2/_Data/AGENTS.md +24 -0
- D4CMPP2/_Data/Aqsoldb.csv +9291 -0
- D4CMPP2/_Data/BradleyMP.csv +3042 -0
- D4CMPP2/_Data/Lipophilicity.csv +1131 -0
- D4CMPP2/_Data/README.md +8 -0
- D4CMPP2/_Data/__init__.py +26 -0
- D4CMPP2/_Data/optical.csv +20237 -0
- D4CMPP2/_Data/test.csv +190 -0
- D4CMPP2/__init__.py +16 -0
- D4CMPP2/__main__.py +5 -0
- D4CMPP2/_main.py +500 -0
- D4CMPP2/cli.py +7 -0
- D4CMPP2/exceptions.py +53 -0
- D4CMPP2/grid_search.py +259 -0
- D4CMPP2/network_refer.yaml +160 -0
- D4CMPP2/networks/AFP_model.py +72 -0
- D4CMPP2/networks/AFPwithSolv_model.py +72 -0
- D4CMPP2/networks/DMPNN_model.py +90 -0
- D4CMPP2/networks/DMPNNwithSolv_model.py +89 -0
- D4CMPP2/networks/GAT_model.py +48 -0
- D4CMPP2/networks/GATwithSolv_model.py +63 -0
- D4CMPP2/networks/GCN_model.py +113 -0
- D4CMPP2/networks/GCNwithSolv_model.py +103 -0
- D4CMPP2/networks/GC_model.py +122 -0
- D4CMPP2/networks/ISATPM_model.py +14 -0
- D4CMPP2/networks/ISATPN_model.py +199 -0
- D4CMPP2/networks/ISAT_model.py +90 -0
- D4CMPP2/networks/MPNN_model.py +56 -0
- D4CMPP2/networks/MPNNwithSolv_model.py +72 -0
- D4CMPP2/networks/__init__.py +25 -0
- D4CMPP2/networks/base.py +250 -0
- D4CMPP2/networks/registry.py +187 -0
- D4CMPP2/networks/src/AFP.py +118 -0
- D4CMPP2/networks/src/BiDropout.py +29 -0
- D4CMPP2/networks/src/DMPNN.py +35 -0
- D4CMPP2/networks/src/GAT.py +69 -0
- D4CMPP2/networks/src/GC.py +85 -0
- D4CMPP2/networks/src/GCN.py +71 -0
- D4CMPP2/networks/src/ISAT.py +153 -0
- D4CMPP2/networks/src/Linear.py +49 -0
- D4CMPP2/networks/src/MPNN.py +56 -0
- D4CMPP2/networks/src/SolventLayer.py +62 -0
- D4CMPP2/networks/src/__init__.py +0 -0
- D4CMPP2/networks/src/distGCN.py +21 -0
- D4CMPP2/networks/src/pyg_hetero.py +24 -0
- D4CMPP2/optimize.py +472 -0
- D4CMPP2/src/Analyzer/ISAAnalyzer.py +458 -0
- D4CMPP2/src/Analyzer/ISAPNAnalyzer.py +366 -0
- D4CMPP2/src/Analyzer/ISAwSAnalyzer.py +117 -0
- D4CMPP2/src/Analyzer/MolAnalyzer.py +319 -0
- D4CMPP2/src/Analyzer/__init__.py +54 -0
- D4CMPP2/src/Analyzer/core.py +480 -0
- D4CMPP2/src/Analyzer/factory.py +166 -0
- D4CMPP2/src/Analyzer/interpretation.py +232 -0
- D4CMPP2/src/Analyzer/results.py +101 -0
- D4CMPP2/src/DataManager/Dataset/GraphDataset.py +314 -0
- D4CMPP2/src/DataManager/Dataset/ISAGraphDataset.py +384 -0
- D4CMPP2/src/DataManager/Dataset/__init__.py +0 -0
- D4CMPP2/src/DataManager/GraphGenerator/ISAGraphGenerator.py +223 -0
- D4CMPP2/src/DataManager/GraphGenerator/MolGraphGenerator.py +73 -0
- D4CMPP2/src/DataManager/GraphGenerator/__init__.py +14 -0
- D4CMPP2/src/DataManager/ISADataManager.py +67 -0
- D4CMPP2/src/DataManager/MolDataManager.py +735 -0
- D4CMPP2/src/DataManager/__init__.py +14 -0
- D4CMPP2/src/DataManager/contracts.py +179 -0
- D4CMPP2/src/NetworkManager/ISANetworkManager.py +12 -0
- D4CMPP2/src/NetworkManager/NetworkManager.py +520 -0
- D4CMPP2/src/NetworkManager/__init__.py +14 -0
- D4CMPP2/src/PostProcessor.py +160 -0
- D4CMPP2/src/TrainManager/ISATrainManager.py +26 -0
- D4CMPP2/src/TrainManager/TrainManager.py +254 -0
- D4CMPP2/src/TrainManager/__init__.py +14 -0
- D4CMPP2/src/TrainManager/callbacks.py +119 -0
- D4CMPP2/src/__init__.py +0 -0
- D4CMPP2/src/utils/PATH.py +246 -0
- D4CMPP2/src/utils/__init__.py +0 -0
- D4CMPP2/src/utils/argparser.py +56 -0
- D4CMPP2/src/utils/checkpointing.py +90 -0
- D4CMPP2/src/utils/config_resolution.py +123 -0
- D4CMPP2/src/utils/config_validation.py +370 -0
- D4CMPP2/src/utils/csv_validation.py +105 -0
- D4CMPP2/src/utils/data_quality.py +181 -0
- D4CMPP2/src/utils/featureizer.py +202 -0
- D4CMPP2/src/utils/functional_group.csv +169 -0
- D4CMPP2/src/utils/graph_cache.py +213 -0
- D4CMPP2/src/utils/leaderboard.py +212 -0
- D4CMPP2/src/utils/metrics.py +31 -0
- D4CMPP2/src/utils/module_loader.py +147 -0
- D4CMPP2/src/utils/output.py +80 -0
- D4CMPP2/src/utils/reproducibility.py +70 -0
- D4CMPP2/src/utils/run_manifest.py +175 -0
- D4CMPP2/src/utils/scaler.py +40 -0
- D4CMPP2/src/utils/sculptor.py +713 -0
- D4CMPP2/src/utils/splitting.py +250 -0
- D4CMPP2/src/utils/supportfile_saver.py +94 -0
- D4CMPP2/src/utils/tools.py +156 -0
- D4CMPP2/src/utils/transfer_learning.py +111 -0
- d4cmpp2-0.4.0.dist-info/METADATA +420 -0
- d4cmpp2-0.4.0.dist-info/RECORD +103 -0
- d4cmpp2-0.4.0.dist-info/WHEEL +5 -0
- d4cmpp2-0.4.0.dist-info/entry_points.txt +2 -0
- d4cmpp2-0.4.0.dist-info/licenses/LICENSE +21 -0
- d4cmpp2-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from prettytable import PrettyTable
|
|
5
|
+
import pickle
|
|
6
|
+
import re
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
from .utils.metrics import get_metrics
|
|
10
|
+
from .utils import tools
|
|
11
|
+
from .utils.output import get_output
|
|
12
|
+
|
|
13
|
+
import traceback
|
|
14
|
+
class PostProcessor:
|
|
15
|
+
"""
|
|
16
|
+
The class for the postprocessing of the trained model.
|
|
17
|
+
This class is used to save the model, learning curve, prediction, and metrics after the training.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config):
|
|
21
|
+
self.config = config
|
|
22
|
+
self.output = get_output(config)
|
|
23
|
+
self.result_path = config['MODEL_PATH']+"/result"
|
|
24
|
+
if not os.path.exists(self.result_path):
|
|
25
|
+
os.makedirs(self.result_path)
|
|
26
|
+
|
|
27
|
+
def postprocess(self, dm, nm, tm, train_loaders, val_loaders, test_loaders):
|
|
28
|
+
"The main function for the postprocessing"
|
|
29
|
+
try:
|
|
30
|
+
self.save_final_model(nm)
|
|
31
|
+
except Exception:
|
|
32
|
+
if self.config.get('legacy_silent_errors', False):
|
|
33
|
+
self.output.error(
|
|
34
|
+
"[Results] Model saving failed while writing the final model."
|
|
35
|
+
)
|
|
36
|
+
self.output.error(traceback.format_exc())
|
|
37
|
+
return
|
|
38
|
+
raise
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
tm.learning_curve.to_csv(os.path.join(self.result_path,"learning_curve.csv"),index=False)
|
|
42
|
+
self.draw_learning_curve(tm.learning_curve)
|
|
43
|
+
except Exception:
|
|
44
|
+
if self.config.get('legacy_silent_errors', False):
|
|
45
|
+
self.output.error("[Results] Failed to save the learning curve.")
|
|
46
|
+
self.output.error(traceback.format_exc())
|
|
47
|
+
else:
|
|
48
|
+
raise
|
|
49
|
+
|
|
50
|
+
self.save_model_summary(nm.network,tm.run_time)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
if self.config.get("save_prediction",True):
|
|
54
|
+
prediction_df = self.save_prediction(nm, tm, train_loaders, val_loaders, test_loaders, scaler=dm.scaler)
|
|
55
|
+
self.plot_prediction(self.config.get("target"),prediction_df)
|
|
56
|
+
self.save_metrics(self.config.get("target"),prediction_df)
|
|
57
|
+
except Exception:
|
|
58
|
+
if self.config.get('legacy_silent_errors', False):
|
|
59
|
+
self.output.error("[Results] Failed to save predictions or metrics.")
|
|
60
|
+
self.output.error(traceback.format_exc())
|
|
61
|
+
else:
|
|
62
|
+
raise
|
|
63
|
+
|
|
64
|
+
def save_prediction(self, nm, tm, train_loader, val_loader, test_loader,scaler=None):
|
|
65
|
+
train_pred, train_true, train_smiles = tm.predict(nm, train_loader)
|
|
66
|
+
val_pred, val_true, val_smiles = tm.predict(nm, val_loader)
|
|
67
|
+
test_pred, test_true, test_smiles = tm.predict(nm, test_loader)
|
|
68
|
+
targets = self.config.get("target")
|
|
69
|
+
if scaler is not None:
|
|
70
|
+
train_true = scaler.inverse_transform(train_true.cpu().numpy())
|
|
71
|
+
val_true = scaler.inverse_transform(val_true.cpu().numpy())
|
|
72
|
+
test_true = scaler.inverse_transform(test_true.cpu().numpy())
|
|
73
|
+
train_pred = scaler.inverse_transform(train_pred.cpu().numpy())
|
|
74
|
+
val_pred = scaler.inverse_transform(val_pred.cpu().numpy())
|
|
75
|
+
test_pred = scaler.inverse_transform(test_pred.cpu().numpy())
|
|
76
|
+
|
|
77
|
+
with open(os.path.join(self.config['MODEL_PATH'],"scaler.pkl"),"wb") as file:
|
|
78
|
+
pickle.dump(scaler,file)
|
|
79
|
+
|
|
80
|
+
prediction_df = None
|
|
81
|
+
for true,pred,smiles,sets in zip([train_true,val_true,test_true],[train_pred,val_pred,test_pred],[train_smiles,val_smiles,test_smiles],['train','val','test']):
|
|
82
|
+
data = {}
|
|
83
|
+
for i, target in enumerate(targets):
|
|
84
|
+
data[f"{target}_pred"] = pred[:, i]
|
|
85
|
+
data[f"{target}_true"] = true[:, i]
|
|
86
|
+
|
|
87
|
+
data.update({k: smiles[k] for k in smiles.keys()})
|
|
88
|
+
data['set'] = sets
|
|
89
|
+
|
|
90
|
+
df = pd.DataFrame(data)
|
|
91
|
+
if prediction_df is None:
|
|
92
|
+
prediction_df = df
|
|
93
|
+
else:
|
|
94
|
+
prediction_df = pd.concat([prediction_df,df],axis=0)
|
|
95
|
+
|
|
96
|
+
prediction_df.to_csv(os.path.join(self.result_path,"prediction.csv"),index=False)
|
|
97
|
+
return prediction_df
|
|
98
|
+
|
|
99
|
+
def save_metrics(self, targets, prediction_df):
|
|
100
|
+
metrics = get_metrics(targets, prediction_df)
|
|
101
|
+
for set in ['train','val','test']:
|
|
102
|
+
table = PrettyTable()
|
|
103
|
+
col = ['target',f'{set}_r2',f'{set}_mae',f'{set}_rmse']
|
|
104
|
+
table.field_names = col
|
|
105
|
+
for t in targets:
|
|
106
|
+
table.add_row([t,metrics[f'{set}_r2'][t],metrics[f'{set}_mae'][t],metrics[f'{set}_rmse'][t]])
|
|
107
|
+
self.output.info(f"[Results] Metrics for split {set!r}:\n{table}")
|
|
108
|
+
|
|
109
|
+
metrics.to_csv(os.path.join(self.result_path,"metrics.csv"))
|
|
110
|
+
|
|
111
|
+
def plot_prediction(self,target, prediction_df):
|
|
112
|
+
img = tools.plot_prediction(target, prediction_df)
|
|
113
|
+
img.save(os.path.join(self.result_path,"prediction.png"))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def save_model_summary(self, model,run_time):
|
|
117
|
+
with open(os.path.join(self.config['MODEL_PATH'],"model_summary.txt"), "w") as file:
|
|
118
|
+
file.write(str(model))
|
|
119
|
+
file.write(f"#params:{sum(p.numel() for p in model.parameters() if p.requires_grad)}")
|
|
120
|
+
file.write("\n")
|
|
121
|
+
file.write(str(model.__class__.__name__))
|
|
122
|
+
file.write("\n")
|
|
123
|
+
file.write(str(model.__class__.__doc__))
|
|
124
|
+
file.write("\n")
|
|
125
|
+
file.write("learning_time: "+str(run_time))
|
|
126
|
+
|
|
127
|
+
def draw_learning_curve(self, learning_curve):
|
|
128
|
+
img = tools.plot_learning_curve(learning_curve['train_loss'],learning_curve['val_loss'])
|
|
129
|
+
img.save(os.path.join(self.result_path,"learning_curve.png"))
|
|
130
|
+
|
|
131
|
+
def clear_checkpoint(self):
|
|
132
|
+
checkpoint_pattern = re.compile(
|
|
133
|
+
r"^param_[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\.pth$"
|
|
134
|
+
)
|
|
135
|
+
for file in os.listdir(self.config['MODEL_PATH']):
|
|
136
|
+
if checkpoint_pattern.fullmatch(file):
|
|
137
|
+
os.remove(os.path.join(self.config['MODEL_PATH'], file))
|
|
138
|
+
|
|
139
|
+
def save_final_model(self, nm):
|
|
140
|
+
"""Atomically commit final.pth before removing package checkpoints."""
|
|
141
|
+
model_path = self.config['MODEL_PATH']
|
|
142
|
+
final_path = os.path.join(model_path, "final.pth")
|
|
143
|
+
staging_path = os.path.join(model_path, f".final.pth.{uuid.uuid4().hex}.tmp")
|
|
144
|
+
try:
|
|
145
|
+
nm.save_params(staging_path)
|
|
146
|
+
if not os.path.isfile(staging_path) or os.path.getsize(staging_path) == 0:
|
|
147
|
+
raise OSError(
|
|
148
|
+
f"Final model staging file {staging_path!r} was not created as a non-empty regular file. "
|
|
149
|
+
"The previous final.pth and best checkpoints were preserved."
|
|
150
|
+
)
|
|
151
|
+
os.replace(staging_path, final_path)
|
|
152
|
+
self.clear_checkpoint()
|
|
153
|
+
finally:
|
|
154
|
+
if os.path.exists(staging_path):
|
|
155
|
+
try:
|
|
156
|
+
os.remove(staging_path)
|
|
157
|
+
except OSError:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
from .TrainManager import Trainer
|
|
8
|
+
|
|
9
|
+
class ISATrainer(Trainer):
|
|
10
|
+
"""Compatibility extension point referenced by ISA registries and saved configs."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
# def get_feature(self, network_manager, loader):
|
|
14
|
+
# "Provide the feature of the model. The model which is used for the training should contains the implementation of 'get_feature'."
|
|
15
|
+
# network_manager.eval()
|
|
16
|
+
# pred_result = {}
|
|
17
|
+
# with torch.no_grad():
|
|
18
|
+
# for loader in tqdm(loader, desc='Calculating features'):
|
|
19
|
+
# torch.autograd.set_detect_anomaly(False)
|
|
20
|
+
# y_pred = network_manager.step(loader,get_feature=True)
|
|
21
|
+
# if pred_result:
|
|
22
|
+
# for key in y_pred:
|
|
23
|
+
# pred_result[key] = torch.cat([pred_result[key],y_pred[key]], dim=0)
|
|
24
|
+
# else:
|
|
25
|
+
# pred_result = y_pred
|
|
26
|
+
# return pred_result
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
from .callbacks import CallbackDispatcher, TrainingEvent
|
|
6
|
+
from D4CMPP2.src.utils.output import get_output
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Trainer():
|
|
10
|
+
"The class where the operation of the training is defined"
|
|
11
|
+
def __init__(self,config):
|
|
12
|
+
self.config = config
|
|
13
|
+
self.output = get_output(config)
|
|
14
|
+
self.model_path=config['MODEL_PATH']
|
|
15
|
+
self.dev=config['device']
|
|
16
|
+
self.max_epoch=config['max_epoch']
|
|
17
|
+
self.learning_curve=None
|
|
18
|
+
self.train_error = None
|
|
19
|
+
self._callback_dispatcher = CallbackDispatcher()
|
|
20
|
+
|
|
21
|
+
def set_callbacks(self, callbacks):
|
|
22
|
+
"""Set runtime-only callbacks without placing objects in saved config."""
|
|
23
|
+
|
|
24
|
+
self._callback_dispatcher = CallbackDispatcher(callbacks)
|
|
25
|
+
|
|
26
|
+
def _event(self, name, network_manager, **kwargs):
|
|
27
|
+
learning_rate = None
|
|
28
|
+
if hasattr(network_manager, "get_lr"):
|
|
29
|
+
learning_rate = float(network_manager.get_lr())
|
|
30
|
+
checkpoint_paths = {}
|
|
31
|
+
model_path = self.config.get("MODEL_PATH")
|
|
32
|
+
if model_path:
|
|
33
|
+
checkpoint_paths = {
|
|
34
|
+
"latest": f"{model_path}/checkpoints/latest.ckpt",
|
|
35
|
+
"best": f"{model_path}/checkpoints/best.ckpt",
|
|
36
|
+
}
|
|
37
|
+
return TrainingEvent(
|
|
38
|
+
name=name,
|
|
39
|
+
learning_rate=learning_rate,
|
|
40
|
+
checkpoint_paths=checkpoint_paths,
|
|
41
|
+
**kwargs,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def train(self, network_manager, train_loader, val_loader):
|
|
45
|
+
|
|
46
|
+
self.learning_curve=getattr(network_manager,'learning_curve',None)
|
|
47
|
+
start_time=time.time()
|
|
48
|
+
start_epoch = getattr(network_manager, "next_epoch", 0)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
self._callback_dispatcher.emit(
|
|
52
|
+
self._event("run_start", network_manager)
|
|
53
|
+
)
|
|
54
|
+
with self.output.progress(
|
|
55
|
+
range(start_epoch, start_epoch + self.max_epoch)
|
|
56
|
+
) as t:
|
|
57
|
+
for epoch in t:
|
|
58
|
+
self._callback_dispatcher.emit(
|
|
59
|
+
self._event("epoch_start", network_manager, epoch=epoch)
|
|
60
|
+
)
|
|
61
|
+
t.set_description('Epoch %d' % epoch)
|
|
62
|
+
epoch_train_loss = self.train_epoch(network_manager, train_loader)
|
|
63
|
+
epoch_train_loss=float(np.mean(epoch_train_loss))
|
|
64
|
+
self._callback_dispatcher.emit(
|
|
65
|
+
self._event(
|
|
66
|
+
"train_epoch_end",
|
|
67
|
+
network_manager,
|
|
68
|
+
epoch=epoch,
|
|
69
|
+
train_loss=epoch_train_loss,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
epoch_val_loss = self.evaluate(network_manager, val_loader)
|
|
73
|
+
epoch_val_loss=float(np.mean(epoch_val_loss))
|
|
74
|
+
self._callback_dispatcher.emit(
|
|
75
|
+
self._event(
|
|
76
|
+
"validation_end",
|
|
77
|
+
network_manager,
|
|
78
|
+
epoch=epoch,
|
|
79
|
+
train_loss=epoch_train_loss,
|
|
80
|
+
val_loss=epoch_val_loss,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
t.set_postfix({'train_loss': epoch_train_loss, 'val_loss': epoch_val_loss})
|
|
85
|
+
|
|
86
|
+
row=pd.Series({'train_loss':epoch_train_loss, 'val_loss': epoch_val_loss})
|
|
87
|
+
if self.learning_curve is None:
|
|
88
|
+
self.learning_curve=pd.DataFrame(row).transpose()
|
|
89
|
+
else:
|
|
90
|
+
self.learning_curve = pd.concat([self.learning_curve,pd.DataFrame(row).transpose()],axis=0)
|
|
91
|
+
self.learning_curve.to_csv(self.model_path+'/learning_curve.csv',index=False)
|
|
92
|
+
stopped = bool(
|
|
93
|
+
network_manager.scheduler_step(
|
|
94
|
+
epoch_val_loss,
|
|
95
|
+
completed_epoch=epoch,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
self._callback_dispatcher.emit(
|
|
99
|
+
self._event(
|
|
100
|
+
"epoch_end",
|
|
101
|
+
network_manager,
|
|
102
|
+
epoch=epoch,
|
|
103
|
+
train_loss=epoch_train_loss,
|
|
104
|
+
val_loss=epoch_val_loss,
|
|
105
|
+
stopped=stopped,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
if stopped:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
except KeyboardInterrupt as error:
|
|
113
|
+
self._callback_dispatcher.emit_failure(
|
|
114
|
+
self._event(
|
|
115
|
+
"interruption",
|
|
116
|
+
network_manager,
|
|
117
|
+
epoch=locals().get("epoch"),
|
|
118
|
+
error=error,
|
|
119
|
+
),
|
|
120
|
+
error,
|
|
121
|
+
)
|
|
122
|
+
if self.config.get('legacy_silent_errors', False):
|
|
123
|
+
self.output.error(
|
|
124
|
+
"[Training] Interrupted by the user; preserving legacy "
|
|
125
|
+
"silent-error behavior."
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
raise
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.train_error = e
|
|
131
|
+
self._callback_dispatcher.emit_failure(
|
|
132
|
+
self._event(
|
|
133
|
+
"exception",
|
|
134
|
+
network_manager,
|
|
135
|
+
epoch=locals().get("epoch"),
|
|
136
|
+
error=e,
|
|
137
|
+
),
|
|
138
|
+
e,
|
|
139
|
+
)
|
|
140
|
+
raise
|
|
141
|
+
|
|
142
|
+
network_manager.load_best_checkpoint()
|
|
143
|
+
self.run_time = time.time()-start_time
|
|
144
|
+
self._callback_dispatcher.emit(
|
|
145
|
+
self._event(
|
|
146
|
+
"run_end",
|
|
147
|
+
network_manager,
|
|
148
|
+
epoch=locals().get("epoch"),
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def train_epoch(self, network_manager, train_loader):
|
|
153
|
+
network_manager.train()
|
|
154
|
+
score_list = []
|
|
155
|
+
target_list = []
|
|
156
|
+
epoch_loss = 0
|
|
157
|
+
count=0
|
|
158
|
+
for loader in (train_loader):
|
|
159
|
+
torch.autograd.set_detect_anomaly(False)
|
|
160
|
+
y, y_pred, loss = network_manager.step(loader)
|
|
161
|
+
|
|
162
|
+
score_list.append(y_pred)
|
|
163
|
+
target_list.append(y)
|
|
164
|
+
|
|
165
|
+
count+=y_pred.shape[0]
|
|
166
|
+
epoch_loss += loss*y_pred.shape[0]
|
|
167
|
+
if count: epoch_loss/=count
|
|
168
|
+
|
|
169
|
+
return epoch_loss
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def evaluate(self, network_manager, val_loader):
|
|
173
|
+
network_manager.eval()
|
|
174
|
+
score_list = []
|
|
175
|
+
target_list = []
|
|
176
|
+
epoch_loss = 0
|
|
177
|
+
count=0
|
|
178
|
+
with torch.no_grad():
|
|
179
|
+
for loader in (val_loader):
|
|
180
|
+
torch.autograd.set_detect_anomaly(False)
|
|
181
|
+
y, y_pred, loss = network_manager.step(loader)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
score_list.append(y_pred)
|
|
185
|
+
target_list.append(y)
|
|
186
|
+
|
|
187
|
+
count+=y_pred.shape[0]
|
|
188
|
+
epoch_loss += loss*y_pred.shape[0]
|
|
189
|
+
if count: epoch_loss/=count
|
|
190
|
+
|
|
191
|
+
return epoch_loss
|
|
192
|
+
|
|
193
|
+
def predict(self, network_manager, pred_loader,dropout=False):
|
|
194
|
+
network_manager.eval()
|
|
195
|
+
if dropout:
|
|
196
|
+
network_manager.dropout_on()
|
|
197
|
+
score_list = []
|
|
198
|
+
target_list = []
|
|
199
|
+
# OLD
|
|
200
|
+
smiles_list = {}
|
|
201
|
+
with torch.no_grad():
|
|
202
|
+
if len(pred_loader) > 10:
|
|
203
|
+
pred_loader = self.output.progress(
|
|
204
|
+
pred_loader,
|
|
205
|
+
desc="Predicting",
|
|
206
|
+
mininterval=1.0,
|
|
207
|
+
)
|
|
208
|
+
for loader in pred_loader:
|
|
209
|
+
torch.autograd.set_detect_anomaly(False)
|
|
210
|
+
y, y_pred, loss,x = network_manager.step(loader,flag=True)
|
|
211
|
+
score_list.append(y_pred)
|
|
212
|
+
target_list.append(y)
|
|
213
|
+
for k in x.keys():
|
|
214
|
+
if k.endswith('_smiles'):
|
|
215
|
+
_k = k[:-7]
|
|
216
|
+
smiles_list[_k] = smiles_list.get(_k,[]) + x[k]
|
|
217
|
+
return torch.cat(score_list, dim=0), torch.cat(target_list, dim=0), smiles_list
|
|
218
|
+
|
|
219
|
+
def get_score(self, network_manager, loader):
|
|
220
|
+
"Provide the score of the model. The model which is used for the training should contains the implementation of 'get_score'."
|
|
221
|
+
network_manager.eval()
|
|
222
|
+
pred_result = {}
|
|
223
|
+
with torch.no_grad():
|
|
224
|
+
if len(loader) > 10:
|
|
225
|
+
loader = self.output.progress(loader, desc="Calculating scores")
|
|
226
|
+
for l in loader:
|
|
227
|
+
torch.autograd.set_detect_anomaly(False)
|
|
228
|
+
y_pred = network_manager.step(l,get_score=True)
|
|
229
|
+
if type(y_pred) is not dict:
|
|
230
|
+
y_pred = {'prediction':y_pred}
|
|
231
|
+
if pred_result:
|
|
232
|
+
for key in y_pred:
|
|
233
|
+
pred_result[key] = torch.cat([pred_result[key],y_pred[key]], dim=0)
|
|
234
|
+
else:
|
|
235
|
+
pred_result = y_pred
|
|
236
|
+
return pred_result
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def get_feature(self, network_manager, loader):
|
|
240
|
+
"Provide the feature of the model. The model which is used for the training should contains the implementation of 'get_feature'."
|
|
241
|
+
network_manager.eval()
|
|
242
|
+
pred_result = {}
|
|
243
|
+
with torch.no_grad():
|
|
244
|
+
if len(loader) > 10:
|
|
245
|
+
loader = self.output.progress(loader, desc="Calculating features")
|
|
246
|
+
for l in loader:
|
|
247
|
+
torch.autograd.set_detect_anomaly(False)
|
|
248
|
+
y_pred = network_manager.step(l,get_feature=True)
|
|
249
|
+
if pred_result:
|
|
250
|
+
for key in y_pred:
|
|
251
|
+
pred_result[key] = torch.cat([pred_result[key],y_pred[key]], dim=0)
|
|
252
|
+
else:
|
|
253
|
+
pred_result = y_pred
|
|
254
|
+
return pred_result
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Lazy exports for built-in training-manager modules."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
|
|
6
|
+
__all__ = ["TrainManager", "ISATrainManager", "callbacks"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def __getattr__(name: str) -> ModuleType:
|
|
10
|
+
if name not in __all__:
|
|
11
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
12
|
+
module = import_module(f"{__name__}.{name}")
|
|
13
|
+
globals()[name] = module
|
|
14
|
+
return module
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Small, observation-only callback contract for the training loop."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
from typing import Any, Mapping, Optional, Sequence, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
TRAINING_EVENTS: Tuple[str, ...] = (
|
|
9
|
+
"run_start",
|
|
10
|
+
"epoch_start",
|
|
11
|
+
"train_epoch_end",
|
|
12
|
+
"validation_end",
|
|
13
|
+
"epoch_end",
|
|
14
|
+
"run_end",
|
|
15
|
+
"exception",
|
|
16
|
+
"interruption",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class TrainingEvent:
|
|
22
|
+
"""Immutable snapshot exposed to callbacks.
|
|
23
|
+
|
|
24
|
+
Model, optimizer, dataloader, and mutable config objects are intentionally
|
|
25
|
+
excluded so callbacks do not become coupled to manager internals.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
name: str
|
|
29
|
+
epoch: Optional[int] = None
|
|
30
|
+
train_loss: Optional[float] = None
|
|
31
|
+
val_loss: Optional[float] = None
|
|
32
|
+
learning_rate: Optional[float] = None
|
|
33
|
+
stopped: bool = False
|
|
34
|
+
checkpoint_paths: Mapping[str, str] = field(default_factory=dict)
|
|
35
|
+
error: Optional[BaseException] = None
|
|
36
|
+
|
|
37
|
+
def __post_init__(self) -> None:
|
|
38
|
+
if self.name not in TRAINING_EVENTS:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
f"Unknown training event {self.name!r}. "
|
|
41
|
+
f"Supported events: {list(TRAINING_EVENTS)!r}."
|
|
42
|
+
)
|
|
43
|
+
object.__setattr__(
|
|
44
|
+
self,
|
|
45
|
+
"checkpoint_paths",
|
|
46
|
+
MappingProxyType(dict(self.checkpoint_paths)),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TrainingCallback:
|
|
51
|
+
"""Optional callback base class.
|
|
52
|
+
|
|
53
|
+
Subclasses may implement ``on_event(event)`` for every event or a specific
|
|
54
|
+
method such as ``on_validation_end(event)``. Specific handlers run first.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def on_event(self, event: TrainingEvent) -> None:
|
|
58
|
+
"""Observe a training event."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EventHistory(TrainingCallback):
|
|
62
|
+
"""Built-in observer that keeps immutable event snapshots in memory."""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self.events: list[TrainingEvent] = []
|
|
66
|
+
|
|
67
|
+
def on_event(self, event: TrainingEvent) -> None:
|
|
68
|
+
self.events.append(event)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CallbackDispatcher:
|
|
72
|
+
"""Validate callbacks and dispatch events in registration order."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, callbacks: Optional[Sequence[Any]] = None) -> None:
|
|
75
|
+
if callbacks is None:
|
|
76
|
+
callbacks = ()
|
|
77
|
+
if isinstance(callbacks, (str, bytes)) or not isinstance(callbacks, Sequence):
|
|
78
|
+
raise TypeError(
|
|
79
|
+
"callbacks must be a sequence of callback objects, for example "
|
|
80
|
+
"[MyCallback()]."
|
|
81
|
+
)
|
|
82
|
+
self.callbacks = tuple(callbacks)
|
|
83
|
+
for index, callback in enumerate(self.callbacks):
|
|
84
|
+
if callback is None:
|
|
85
|
+
raise TypeError(f"callbacks[{index}] is None; provide a callback object.")
|
|
86
|
+
has_generic = callable(getattr(callback, "on_event", None))
|
|
87
|
+
has_specific = any(
|
|
88
|
+
callable(getattr(callback, f"on_{name}", None))
|
|
89
|
+
for name in TRAINING_EVENTS
|
|
90
|
+
)
|
|
91
|
+
if not has_generic and not has_specific:
|
|
92
|
+
raise TypeError(
|
|
93
|
+
f"callbacks[{index}] ({type(callback).__name__}) does not define "
|
|
94
|
+
"on_event(event) or a supported on_<event>(event) method."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def emit(self, event: TrainingEvent) -> None:
|
|
98
|
+
"""Dispatch one immutable event, propagating callback failures."""
|
|
99
|
+
|
|
100
|
+
for callback in self.callbacks:
|
|
101
|
+
specific = getattr(callback, f"on_{event.name}", None)
|
|
102
|
+
if callable(specific):
|
|
103
|
+
specific(event)
|
|
104
|
+
generic = getattr(callback, "on_event", None)
|
|
105
|
+
if callable(generic):
|
|
106
|
+
generic(event)
|
|
107
|
+
|
|
108
|
+
def emit_failure(self, event: TrainingEvent, original: BaseException) -> None:
|
|
109
|
+
"""Notify failure handlers without hiding the original exception."""
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
self.emit(event)
|
|
113
|
+
except BaseException as callback_error:
|
|
114
|
+
add_note = getattr(original, "add_note", None)
|
|
115
|
+
if callable(add_note):
|
|
116
|
+
add_note(
|
|
117
|
+
"A callback failure handler also failed: "
|
|
118
|
+
f"{type(callback_error).__name__}: {callback_error}"
|
|
119
|
+
)
|
D4CMPP2/src/__init__.py
ADDED
|
File without changes
|