AutoML for Regression Model Screening

The following are the steps for using AutoML for a regression task:

Note: Setting the flag for featurization= ‘True’ generates represents molecules using 5 representation techniques.

  1. Requires an input pandas dataframe consisting of two columns:

    • SMILES strings

    • target property values

  2. If featurization = True, Molecules are represented as:

    • Coulomb matrix

    • RDKit Morgan (ECFP6 1024-bit) fingerprints

    • MACCs keys

    • RDKit hashed topological torsion

    • RDKit molecular descriptors (all)

    • Mordred molecular descriptors (currently using base Mordred)

  3. Screens through various regressor models:

Yields ‘n-best’ models, with optimized hyperparamters.

The output_dir argument specifies a folder that will be created wherever the code is run from, containing the following directory tree:

 <output_dir>/
|-- output.log
|-- scores.csv
|-- best_model/
| |-- model.pkl # most non-MLP models
| |-- model_chemml_model.json # MLP case (plus possible extra model files)
| |-- scaler.pkl # present when scaler exists for chosen feature_key
| |-- feature_order.json
| |-- metadata.json
|-- .model_screener_artifacts/
| |-- <run_key_1>/
| |-- model.pkl # or MLP artifact files
| |-- <run_key_2>/
...

Load your data

We are using ChemML’s in-built dataset to demonstrate the minimum requirements to run AutoML.

[1]:
import pandas as pd
import numpy as np
from chemml.chem import Molecule
from chemml.datasets import load_organic_density
[2]:
molecules, target, dragon_subset = load_organic_density()
df=pd.concat([molecules, target], axis=1)
df = df.sample(100, random_state=42)
df
[2]:
smiles density_Kg/m3
361 c1scc(n1)c1ncc(s1)c1c(ccc2c1cccc2)c1cncs1 1328.35
73 S1CC(SC(C1)C1CCCC1)c1cocc1c1ccccc1 1143.80
374 N1CNC(NC1)c1sccc1C1(CSCCS1)c1cccs1 1351.33
155 c1nc(c(s1)c1nsnc1)c1csc(n1)c1cccs1 1466.57
104 Oc1ccc2c(c1c1cocc1)c(ccc2)c1ccccn1 1207.67
... ... ...
347 c1ccc(cn1)c1ncsc1c1scnc1c1ccc2c(c1)cccc2 1269.53
86 c1cnc(cn1)c1coc(c1)c1nccc(c1)c1cccc2c1cccc2 1209.81
75 OC1NCN(CN1)c1cccc2c1ccc(c2)C1CSCCS1 1282.86
438 Sc1cc2c(cc1c1ccc3c(c1)cccc3)cccc2C1CCCC1 1122.68
15 CC1CCC(C1)C1CCCC1c1cccs1 1005.60

100 rows × 2 columns

Run autoML for a regression task

Since we have not provided any feature set, we can use AutoML’s in-built featurization to screen through feature representations and find the most suitable feature set for our data.

[3]:
from chemml.autoML import ModelScreener
MS = ModelScreener(df, target="density_Kg/m3", featurization=True, smiles="smiles",
                   screener_type="regressor", output_dir="automl_results")
scores = MS.screen_models(n_best=10, multi_core=False)
Converting SMILES to ChemML Molecule objects: 100%|██████████| 100/100 [00:03<00:00, 28.20it/s]
featurizing molecules in batches of 6 ...
100/100 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13s 126ms/step
Merging batch features ...    [DONE]
Calculating RDKit descriptors: 100%|██████████| 100/100 [00:01<00:00, 85.99it/s]
Normalizing Molecule input: 100%|██████████| 100/100 [00:00<?, ?it/s]
100%|██████████| 100/100 [00:02<00:00, 48.08it/s]

Cleaning feature sets to remove constant, highly correlated, and low-variance features...

Feature set 'CoulombMatrix' cleaned: 26 features retained.
Feature set 'morganfingerprints_radius3' cleaned: 247 features retained.
Feature set 'MACCS_radius3' cleaned: 48 features retained.
Feature set 'hashedtopologicaltorsion_radius3' cleaned: 82 features retained.
Feature set 'rdkit_descriptors' cleaned: 92 features retained.
Feature set 'mord_descriptors' cleaned: 409 features retained.
split done!
Single-core complete


------------------------- Screening complete for feature set CoulombMatrix, time taken: 8.581 seconds -------------------------

split done!
Single-core complete


------------------------- Screening complete for feature set morganfingerprints_radius3, time taken: 9.818 seconds -------------------------

split done!
Single-core complete


------------------------- Screening complete for feature set MACCS_radius3, time taken: 9.35 seconds -------------------------

split done!
Single-core complete


------------------------- Screening complete for feature set hashedtopologicaltorsion_radius3, time taken: 9.209 seconds -------------------------

split done!
Single-core complete


------------------------- Screening complete for feature set rdkit_descriptors, time taken: 11.241 seconds -------------------------

split done!
Single-core complete


------------------------- Screening complete for feature set mord_descriptors, time taken: 11.417 seconds -------------------------

[4]:
scores
[4]:
ME MAE MSE RMSE MSLE RMSLE MAPE MaxAPE RMSPE MPE MaxAE deltaMaxE r_squared std time(seconds) Model parameters Feature run_key
24 -1.165839 6.548663 64.357274 8.022299 0.000041 0.006411 0.519401 1.227364 0.642961 -0.109150 13.696279 25.721771 0.990377 81.777502 0.779308 Ridge {'alpha': 24.6, 'copy_X': True, 'fit_intercept... mord_descriptors Ridge_mord_descriptors_c1fb1891b1d34cbdaf986cc...
28 -1.780404 7.570432 65.826309 8.113341 0.000040 0.006317 0.594852 1.035899 0.632973 -0.146564 13.760363 23.502871 0.990157 81.777502 3.895243 ElasticNet {'alpha': 0.10000000000000002, 'copy_X': True,... mord_descriptors ElasticNet_mord_descriptors_8d735767e52c42c79a...
27 -0.832714 8.689409 104.796914 10.237036 0.000060 0.007748 0.669774 1.270207 0.776550 -0.059801 17.726883 31.832373 0.984330 81.777502 2.997654 Lasso {'alpha': 0.10000000000000002, 'copy_X': True,... mord_descriptors Lasso_mord_descriptors_7c5ff0e6fb9e44a5857ee55...
26 -3.892511 9.395611 137.754552 11.736889 0.000085 0.009234 0.742650 1.788322 0.924081 -0.295887 21.893891 42.189124 0.979401 81.777502 1.260182 SVR {'C': 6.2105263157894735, 'cache_size': 200, '... mord_descriptors SVR_mord_descriptors_82340791fa3b4210aa16ae5c4...
21 -4.271152 13.444280 214.341436 14.640404 0.000137 0.011693 1.074177 1.532998 1.172778 -0.373718 19.344591 37.910187 0.967949 81.777502 1.469364 Lasso {'alpha': 0.10000000000000002, 'copy_X': True,... rdkit_descriptors Lasso_rdkit_descriptors_5b8c455e3bca409685435f...
22 -6.941877 14.424102 272.929168 16.520568 0.000174 0.013208 1.154577 2.078724 1.329172 -0.588298 25.994649 44.270452 0.959189 81.777502 1.616326 ElasticNet {'alpha': 0.10000000000000002, 'copy_X': True,... rdkit_descriptors ElasticNet_rdkit_descriptors_93cd6f24bbf0420b8...
23 -8.077278 16.409874 306.237973 17.499656 0.000195 0.013954 1.306025 2.220779 1.404272 -0.671101 27.389085 45.952560 0.954208 81.777502 3.900002 SVR {'C': 11.421052631578947, 'cache_size': 200, '... rdkit_descriptors SVR_rdkit_descriptors_31fa85c2a77d4ba59b870eae...
19 -8.890544 15.248120 310.362742 17.617115 0.000201 0.014181 1.227860 2.276451 1.429814 -0.749505 28.075700 44.082699 0.953591 81.777502 0.583344 Ridge {'alpha': 9.9, 'copy_X': True, 'fit_intercept'... rdkit_descriptors Ridge_rdkit_descriptors_98d7a8cc1d394506be9458...
25 -8.837472 28.483415 954.271945 30.891292 0.000601 0.024515 2.273317 4.412876 2.476895 -0.675980 54.890000 85.104286 0.857306 81.777502 0.850017 DecisionTreeRegressor {'ccp_alpha': 0.0, 'criterion': 'squared_error... mord_descriptors DecisionTreeRegressor_mord_descriptors_1e6ffed...
10 13.175179 25.564536 1008.856455 31.762501 0.000573 0.023947 1.947052 4.211498 2.377639 0.950111 58.775247 105.898937 0.849144 81.777502 1.257556 Lasso {'alpha': 0.10000000000000002, 'copy_X': True,... MACCS_radius3 Lasso_MACCS_radius3_1a18e958419847799443b8c6eb...

Validating AutoML-generated model

Since ChemML v1.3.4, AutoML outputs the best model and required scaler and feature order as easy-to-import files. A quick demonstration of the model loading workflow is given below:

[5]:
import pickle
import json

with open('automl_results/best_model/metadata.json', 'r') as f:
    metadata = json.load(f)
with open('automl_results/best_model/feature_order.json', 'r') as f:
    feature_order = json.load(f)
with open("automl_results/best_model/model.pkl", "rb") as f:
    loaded_model = pickle.load(f)
with open("automl_results/best_model/scaler.pkl", "rb") as f:
    loaded_scaler = pickle.load(f)

Metadata isn’t necessary for model validation, but gives us a lot of context from the AutoML screening workflow.

[6]:
metadata
[6]:
{'artifact_schema_version': 1,
 'model_name': 'Ridge',
 'feature_key': 'mord_descriptors',
 'run_key': 'Ridge_mord_descriptors_c1fb1891b1d34cbdaf986cca0c5375d6',
 'screener_type': 'regressor',
 'parameters': {'alpha': 24.6,
  'copy_X': True,
  'fit_intercept': True,
  'max_iter': None,
  'positive': False,
  'random_state': None,
  'solver': 'auto',
  'tol': 0.0001},
 'metrics': {'ME': -1.1658386080855963,
  'MAE': 6.5486628603416195,
  'MSE': 64.35727445359389,
  'RMSE': 8.022298576691963,
  'MSLE': 4.1095421280610533e-05,
  'RMSLE': 0.006410571057293612,
  'MAPE': 0.5194005241242112,
  'MaxAPE': 1.227364087808024,
  'RMSPE': 0.642960942887699,
  'MPE': -0.10914957390993163,
  'MaxAE': 13.696278592258523,
  'deltaMaxE': 25.72177133371156,
  'r_squared': 0.9903765684081806,
  'std': 81.77750192442905,
  'time(seconds)': 0.7793076038360596},
 'model_serializer': 'pickle',
 'model_artifact_dir': 'model_artifact',
 'scaler_file': 'scaler.pkl',
 'feature_order_file': 'feature_order.json',
 'timestamp': 'Mon Jul 27 13:59:05 2026'}

feature_order ensures that the trained model sees features in the exact same order as trained.

[7]:
feature_order[:5]
[7]:
['nBase', 'SpAbs_A', 'SpDiam_A', 'VE1_A', 'VR1_A']
[8]:
from chemml.chem import Molecule
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import tqdm
from chemml.chem import Mordred
from sklearn.svm import SVR

mol_objs_list = []
for i, smi in enumerate(tqdm.tqdm(df["smiles"], desc="Converting SMILES to ChemML Molecule objects")):
    mol = Molecule(smi, 'smiles')
    mol.hydrogens('add')
    try:
        mol.to_xyz('MMFF', maxIters=10000, mmffVariant='MMFF94s')
        mol_objs_list.append(mol)
    except Exception as e:
        print(f"\nUnable to process SMILES: {smi}; Error: {e}")
        # self.discarded_indices.append(i)
loaded_mols = mol_objs_list
features = Mordred().represent(loaded_mols).drop(columns=['SMILES'])[feature_order].values
targets = df["density_Kg/m3"].values


X_test = loaded_scaler.transform(features)

y_pred = loaded_model.predict(X_test)
Converting SMILES to ChemML Molecule objects: 100%|██████████| 100/100 [00:03<00:00, 28.33it/s]

The loaded_model object can be treated just like any model object

[9]:
loaded_model
[9]:
Ridge(alpha=24.6)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
[10]:
targets[:5], y_pred[:5]
[10]:
(array([1328.35, 1143.8 , 1351.33, 1466.57, 1207.67]),
 array([1339.22902423, 1145.02145831, 1342.43338352, 1469.29736858,
        1204.22178333]))
[11]:
from chemml.utils import regression_metrics
regression_metrics(targets, y_pred)
[11]:
ME MAE MSE RMSE MSLE RMSLE MAPE MaxAPE RMSPE MPE MaxAE deltaMaxE r_squared std
0 -0.116584 4.755083 34.813914 5.900332 0.000023 0.004802 0.381633 1.369011 0.480841 -0.018473 14.131558 27.898336 0.995465 87.617825
[12]:
from chemml.visualization import scatter2D, SavePlot, decorator
import pandas as pd
%matplotlib inline
df_viz = pd.DataFrame()
df_viz["Actual"] = targets.reshape(-1,)
df_viz["Predicted"] = y_pred.reshape(-1,)
sc = scatter2D('r', marker='.')
fig = sc.plot(dfx=df_viz, dfy=df_viz, x="Actual", y="Predicted")

dec = decorator(title='Actual vs. Predicted',xlabel='Actual Density', ylabel='Predicted Density',
                xlim= (950,1550), ylim=(950,1550), grid=True,
                grid_color='g', grid_linestyle=':', grid_linewidth=0.5)
fig = dec.fit(fig)
# print(type(fig))
sa=SavePlot(filename='Parity',output_directory='./automl_results/val/images',
            kwargs={'facecolor':'w','dpi':330,'pad_inches':0.1, 'bbox_inches':'tight'})
sa.save(obj=fig)
fig.show()
The Plot has been saved at:  .\./automl_results/val/images/Parity.png
../_images/ipython_notebooks_autoML_regression_18_1.png