diff --git a/.dockerignore b/.dockerignore
old mode 100644
new mode 100755
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
old mode 100644
new mode 100755
index 27f43c34..5bbea609
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -101,5 +101,17 @@ jobs:
exit 1
fi
+ dfpl interpretgnn -f example/interpret.json
+ if [ "$(cat interpretations.csv | wc -l)" -lt "6" ]; then
+ echo "predict result should have at least 5 lines. But had only $(cat interpretations.csv | wc -l)" >&2
+ exit 1
+ fi
+
+ dfpl convert -f tests/data
+ if [ "$(find tests/data -name '*.csv' | wc -l)" -ne "$(find tests/data -name '*.pkl' | wc -l)" ]; then
+ echo "not all csv files are converted to pickle ones" >&2
+ exit 1
+ fi
+
- dfpl convert -f tests/data
\ No newline at end of file
+ echo "All tests passed"
\ No newline at end of file
diff --git a/.github/workflows/push-to-gitlab.yml b/.github/workflows/push-to-gitlab.yml
old mode 100644
new mode 100755
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
old mode 100644
new mode 100755
diff --git a/LICENSE.pdf b/LICENSE.pdf
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index bf3ed816..64d330c0
--- a/README.md
+++ b/README.md
@@ -111,22 +111,23 @@ In order to use the environment it needs to be activated with `. ENV_PATH/bin/ac
To use this tool in a conda environment:
-1. Create the conda env from scratch
+1. Install mamba. For details follow the installation guide here. https://mamba.readthedocs.io/en/latest/mamba-installation.html#mamba-install}
+2. Create the mamba env from scratch
From within the `deepFPlearn` directory, you can create the conda environment with the provided yaml file that
contains all information and necessary packages
```shell
- conda env create -f environment.yml
+ mamba env create -f environment.yml
```
-2. Activate the `dfpl_env` environment with
+3. Activate the `dfpl_env` environment with
```shell
- conda activate dfpl_env
+ mamba activate dfpl_env
```
-3. Install the local `dfpl` package by calling
+4. Install the local `dfpl` package by calling
```shell
pip install --no-deps ./
@@ -327,7 +328,6 @@ Kyriakos Soulios, Patrick Scheibe, Matthias Bernt, Jörg Hackermüller, and Jana
deepFPlearn+: Enhancing Toxicity Prediction Across the Chemical Universe Using Graph Neural Networks.
Submitted to a scientific journal, currently under review.
-[2]
Jana Schor, Patrick Scheibe, Matthias Bernt, Wibke Busch, Chih Lai, and Jörg Hackermüller.
AI for predicting chemical-effect associations at the chemical universe level—deepFPlearn.
Briefings in Bioinformatics, Volume 23, Issue 5, September 2022, bbac257, https://doi.org/10.1093/bib/bbac257
diff --git a/container/Dockerfile b/container/Dockerfile
old mode 100644
new mode 100755
index 566685ad..d0165580
--- a/container/Dockerfile
+++ b/container/Dockerfile
@@ -50,6 +50,7 @@ RUN sh -c 'echo "APT { Get { AllowUnauthenticated \"1\"; }; };" > /etc/apt/apt.c
RUN apt -o Acquire::AllowInsecureRepositories=true -o Acquire::AllowDowngradeToInsecureRepositories=true update
RUN apt-get install -y curl wget
+RUN apt-get install -y git
RUN apt-key del 7fa2af80
RUN wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.0-1_all.deb
@@ -118,7 +119,7 @@ RUN ln -s $(which python3) /usr/local/bin/python
# does not work sind it just copies the files in dfpl
COPY ./ /deepFPlearn/
-# install dfpl
+# install dfpl
RUN python -m pip install --no-cache-dir /deepFPlearn && pip install --no-cache-dir pytest
# The code to run when container is started.
diff --git a/container/README.md b/container/README.md
old mode 100644
new mode 100755
diff --git a/dfpl/__init__.py b/dfpl/__init__.py
old mode 100644
new mode 100755
diff --git a/dfpl/__main__.py b/dfpl/__main__.py
index 7896d451..f9aa715e 100755
--- a/dfpl/__main__.py
+++ b/dfpl/__main__.py
@@ -1,12 +1,10 @@
import dataclasses
import logging
-import os.path
-import pathlib
+import os
from argparse import Namespace
from os import path
-
-import chemprop as cp
-import pandas as pd
+import wandb
+import chemprop
from keras.models import load_model
from dfpl import autoencoder as ac
@@ -17,43 +15,8 @@
from dfpl import vae as vae
from dfpl.utils import createArgsFromJson, createDirectory, makePathAbsolute
-project_directory = pathlib.Path(".").parent.parent.absolute()
-test_train_opts = options.Options(
- inputFile=f"{project_directory}/input_datasets/S_dataset.pkl",
- outputDir=f"{project_directory}/output_data/console_test",
- ecWeightsFile=f"{project_directory}/output_data/case_00/AE_S/ae_S.encoder.hdf5",
- ecModelDir=f"{project_directory}/output_data/case_00/AE_S/saved_model",
- type="smiles",
- fpType="topological",
- epochs=100,
- batchSize=1024,
- fpSize=2048,
- encFPSize=256,
- enableMultiLabel=False,
- testSize=0.2,
- kFolds=2,
- verbose=2,
- trainAC=False,
- trainFNN=True,
- compressFeatures=True,
- activationFunction="selu",
- lossFunction="bce",
- optimizer="Adam",
- fnnType="FNN",
-)
-
-test_pred_opts = options.Options(
- inputFile=f"{project_directory}/input_datasets/S_dataset.pkl",
- outputDir=f"{project_directory}/output_data/console_test",
- outputFile=f"{project_directory}/output_data/console_test/S_dataset.predictions_ER.csv",
- ecModelDir=f"{project_directory}/output_data/case_00/AE_S/saved_model",
- fnnModelDir=f"{project_directory}/output_data/console_test/ER_saved_model",
- type="smiles",
- fpType="topological",
-)
-
-
-def traindmpnn(opts: options.GnnOptions):
+
+def traindmpnn(opts: options.GnnOptions) -> None:
"""
Train a D-MPNN model using the given options.
Args:
@@ -61,54 +24,46 @@ def traindmpnn(opts: options.GnnOptions):
Returns:
- None
"""
- os.environ["CUDA_VISIBLE_DEVICES"] = f"{opts.gpu}"
- ignore_elements = ["py/object"]
# Load options from a JSON file and replace the relevant attributes in `opts`
- arguments = createArgsFromJson(
- opts.configFile, ignore_elements, return_json_object=False
- )
- opts = cp.args.TrainArgs().parse_args(arguments)
+ arguments = createArgsFromJson(jsonFile=opts.configFile)
+ opts = chemprop.args.TrainArgs().parse_args(arguments)
logging.info("Training DMPNN...")
- # Train the model and get the mean and standard deviation of AUC score from cross-validation
- mean_score, std_score = cp.train.cross_validate(
- args=opts, train_func=cp.train.run_training
+ mean_score, std_score = chemprop.train.cross_validate(
+ args=opts, train_func=chemprop.train.run_training
)
logging.info(f"Results: {mean_score:.5f} +/- {std_score:.5f}")
-def predictdmpnn(opts: options.GnnOptions, json_arg_path: str) -> None:
+def predictdmpnn(opts: options.GnnOptions) -> None:
"""
Predict the values using a trained D-MPNN model with the given options.
Args:
- opts: options.GnnOptions instance containing the details of the prediction
- - JSON_ARG_PATH: path to a JSON file containing additional arguments for prediction
Returns:
- None
"""
- ignore_elements = [
- "py/object",
- "checkpoint_paths",
- "save_dir",
- "saving_name",
- ]
# Load options and additional arguments from a JSON file
- arguments, data = createArgsFromJson(
- json_arg_path, ignore_elements, return_json_object=True
+ arguments = createArgsFromJson(jsonFile=opts.configFile)
+ opts = chemprop.args.PredictArgs().parse_args(arguments)
+
+ chemprop.train.make_predictions(args=opts)
+
+
+def interpretdmpnn(opts: options.GnnOptions) -> None:
+ """
+ Interpret the predictions of a trained D-MPNN model with the given options.
+ Args:
+ - opts: options.GnnOptions instance containing the details of the prediction
+ Returns:
+ - None
+ """
+ # Load options and additional arguments from a JSON file
+ arguments = createArgsFromJson(jsonFile=opts.configFile)
+ opts = chemprop.args.InterpretArgs().parse_args(arguments)
+
+ chemprop.interpret.interpret(
+ args=opts, save_to_csv=True
)
- arguments.append("--preds_path")
- arguments.append("")
- save_dir = data.get("save_dir")
- name = data.get("saving_name")
- # Replace relevant attributes in `opts` with loaded options
- opts = cp.args.PredictArgs().parse_args(arguments)
- opts.preds_path = save_dir + "/" + name
- df = pd.read_csv(opts.test_path)
- smiles = []
- for index, rows in df.iterrows():
- my_list = [rows.smiles]
- smiles.append(my_list)
- # Make predictions and return the result
- cp.train.make_predictions(args=opts, smiles=smiles)
def train(opts: options.Options):
@@ -116,9 +71,6 @@ def train(opts: options.Options):
Run the main training procedure
:param opts: Options defining the details of the training
"""
-
- os.environ["CUDA_VISIBLE_DEVICES"] = f"{opts.gpu}"
-
# import data from file and create DataFrame
if "tsv" in opts.inputFile:
df = fp.importDataFile(
@@ -128,7 +80,7 @@ def train(opts: options.Options):
df = fp.importDataFile(
opts.inputFile, import_function=fp.importSmilesCSV, fp_size=opts.fpSize
)
- # initialize encoders to None
+ # initialize (auto)encoders to None
encoder = None
autoencoder = None
if opts.trainAC:
@@ -142,26 +94,32 @@ def train(opts: options.Options):
# if feature compression is enabled
if opts.compressFeatures:
if not opts.trainAC:
- if opts.aeType == "deterministic":
- (autoencoder, encoder) = ac.define_ac_model(opts=options.Options())
- elif opts.aeType == "variational":
+ if opts.aeType == "variational":
(autoencoder, encoder) = vae.define_vae_model(opts=options.Options())
- elif opts.ecWeightsFile == "":
+ else:
+ (autoencoder, encoder) = ac.define_ac_model(opts=options.Options())
+
+ if opts.ecWeightsFile == "":
encoder = load_model(opts.ecModelDir)
else:
autoencoder.load_weights(
os.path.join(opts.ecModelDir, opts.ecWeightsFile)
)
+
# compress the fingerprints using the autoencoder
df = ac.compress_fingerprints(df, encoder)
- # ac.visualize_fingerprints(
- # df,
- # before_col="fp",
- # after_col="fpcompressed",
- # train_indices=train_indices,
- # test_indices=test_indices,
- # save_as=f"UMAP_{opts.aeSplitType}.png",
- # )
+ if opts.visualizeLatent and opts.trainAC:
+ ac.visualize_fingerprints(
+ df,
+ save_as=f"{opts.ecModelDir}/TSNE_{opts.aeType}_{opts.aeSplitType}.png",
+ )
+ elif opts.visualizeLatent:
+ logging.info(
+ "Visualizing latent space is only available if you train the autoencoder. Skipping visualization."
+ )
+ if opts.trainFNN and opts.finetuneEncoder:
+ sl.train_single_label_models(df=df, opts=opts)
+
# train single label models if requested
if opts.trainFNN and not opts.enableMultiLabel:
sl.train_single_label_models(df=df, opts=opts)
@@ -257,29 +215,36 @@ def main():
raise ValueError("Input directory is not a directory")
elif prog_args.method == "traingnn":
traingnn_opts = options.GnnOptions.fromCmdArgs(prog_args)
-
+ createLogger("traingnn.log")
traindmpnn(traingnn_opts)
elif prog_args.method == "predictgnn":
- predictgnn_opts = options.GnnOptions.fromCmdArgs(prog_args)
- fixed_opts = dataclasses.replace(
- predictgnn_opts,
- test_path=makePathAbsolute(predictgnn_opts.test_path),
- preds_path=makePathAbsolute(predictgnn_opts.preds_path),
- )
-
- logging.info(
- f"The following arguments are received or filled with default values:\n{prog_args}"
- )
-
- predictdmpnn(fixed_opts, prog_args.configFile)
+ predictgnn_opts = options.PredictGnnOptions.fromCmdArgs(prog_args)
+ createLogger("predictgnn.log")
+ predictdmpnn(predictgnn_opts)
+ elif prog_args.method == "interpretgnn":
+ interpretgnn_opts = options.InterpretGNNoptions.fromCmdArgs(prog_args)
+ createLogger("interpretgnn.log")
+ interpretdmpnn(interpretgnn_opts)
elif prog_args.method == "train":
+ if prog_args.configFile is None and prog_args.inputFile is None:
+ parser.error("Either --configFile or --inputFile must be provided.")
+
train_opts = options.Options.fromCmdArgs(prog_args)
+ # Access wandb configuration
+ # wandb.init(project="dfpl")
+ # config = wandb.config
+
fixed_opts = dataclasses.replace(
train_opts,
inputFile=makePathAbsolute(train_opts.inputFile),
outputDir=makePathAbsolute(train_opts.outputDir),
+ # learningRate=config.learningRate,
+ # learningRateDecay=config.learningRateDecay,
+ # dropout=config.dropout,
+ # batchSize=config.batchSize,
+ # l2reg=config.l2reg
)
createDirectory(fixed_opts.outputDir)
createLogger(path.join(fixed_opts.outputDir, "train.log"))
@@ -288,6 +253,8 @@ def main():
)
train(fixed_opts)
elif prog_args.method == "predict":
+ if prog_args.configFile is None and prog_args.inputFile is None:
+ parser.error("Either --configFile or --inputFile must be provided.")
predict_opts = options.Options.fromCmdArgs(prog_args)
fixed_opts = dataclasses.replace(
predict_opts,
@@ -298,8 +265,6 @@ def main():
),
ecModelDir=makePathAbsolute(predict_opts.ecModelDir),
fnnModelDir=makePathAbsolute(predict_opts.fnnModelDir),
- trainAC=False,
- trainFNN=False,
)
createDirectory(fixed_opts.outputDir)
createLogger(path.join(fixed_opts.outputDir, "predict.log"))
diff --git a/dfpl/autoencoder.py b/dfpl/autoencoder.py
old mode 100644
new mode 100755
index 99bf4578..d96cd48c
--- a/dfpl/autoencoder.py
+++ b/dfpl/autoencoder.py
@@ -1,19 +1,18 @@
import logging
import math
import os.path
-from os.path import basename
from typing import Tuple
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
-import umap
+import umap.umap_ as umap
import wandb
from sklearn.model_selection import train_test_split
from tensorflow.keras import initializers, losses, optimizers
from tensorflow.keras.layers import Dense, Input
-from tensorflow.keras.models import Model
+from tensorflow.keras.models import Model, load_model
from dfpl import callbacks
from dfpl import history as ht
@@ -32,9 +31,13 @@ def define_ac_model(opts: options.Options, output_bias=None) -> Tuple[Model, Mod
"""
input_size = opts.fpSize
encoding_dim = opts.encFPSize
- ac_optimizer = optimizers.Adam(
- learning_rate=opts.aeLearningRate, decay=opts.aeLearningRateDecay
+ lr_schedule = optimizers.schedules.ExponentialDecay(
+ opts.aeLearningRate,
+ decay_steps=1000,
+ decay_rate=opts.aeLearningRateDecay,
+ staircase=True,
)
+ ac_optimizer = optimizers.legacy.Adam(learning_rate=lr_schedule)
if output_bias is not None:
output_bias = initializers.Constant(output_bias)
@@ -104,7 +107,6 @@ def define_ac_model(opts: options.Options, output_bias=None) -> Tuple[Model, Mod
)(decoded)
# output layer
- # to either 0 or 1 and hence we use sigmoid activation function.
decoded = Dense(
units=input_size, activation="sigmoid", bias_initializer=output_bias
)(decoded)
@@ -119,19 +121,13 @@ def define_ac_model(opts: options.Options, output_bias=None) -> Tuple[Model, Mod
encoder = Model(input_vec, encoded)
autoencoder.summary(print_fn=logging.info)
- autoencoder.compile(
- optimizer=ac_optimizer,
- loss=losses.BinaryCrossentropy(),
- # metrics=[
- # metrics.AUC(),
- # metrics.Precision(),
- # metrics.Recall()
- # ]
- )
+ autoencoder.compile(optimizer=ac_optimizer, loss=losses.BinaryCrossentropy())
return autoencoder, encoder
-def train_full_ac(df: pd.DataFrame, opts: options.Options) -> Model:
+def train_full_ac(
+ df: pd.DataFrame, opts: options.Options
+) -> Tuple[Model, np.ndarray, np.ndarray]:
"""
Trains an autoencoder on the given feature matrix X. The response matrix is only used to
split the data into meaningful test and train sets.
@@ -145,37 +141,8 @@ def train_full_ac(df: pd.DataFrame, opts: options.Options) -> Model:
if opts.aeWabTracking and not opts.wabTracking:
wandb.init(project=f"AE_{opts.aeSplitType}")
- # Define output files for autoencoder and encoder weights
- if opts.ecWeightsFile == "":
- # If no encoder weights file is specified, use the input file name to generate a default file name
- logging.info("No AE encoder weights file specified")
- base_file_name = (
- os.path.splitext(basename(opts.inputFile))[0] + opts.aeSplitType
- )
- logging.info(
- f"(auto)encoder weights will be saved in {base_file_name}.autoencoder.hdf5"
- )
- ac_weights_file = os.path.join(
- opts.outputDir, base_file_name + ".autoencoder.weights.hdf5"
- )
- # ec_weights_file = os.path.join(
- # opts.outputDir, base_file_name + ".encoder.weights.hdf5"
- # )
- else:
- # If an encoder weights file is specified, use it as the encoder weights file name
- logging.info(f"AE encoder will be saved in {opts.ecWeightsFile}")
- base_file_name = (
- os.path.splitext(basename(opts.ecWeightsFile))[0] + opts.aeSplitType
- )
- ac_weights_file = os.path.join(
- opts.outputDir, base_file_name + ".autoencoder.weights.hdf5"
- )
- # ec_weights_file = os.path.join(opts.outputDir, opts.ecWeightsFile)
-
+ save_path = os.path.join(opts.ecModelDir, f"{opts.aeSplitType}_split_autoencoder")
# Collect the callbacks for training
- callback_list = callbacks.autoencoder_callback(
- checkpoint_path=ac_weights_file, opts=opts
- )
# Select all fingerprints that are valid and turn them into a numpy array
fp_matrix = np.array(
@@ -257,7 +224,6 @@ def train_full_ac(df: pd.DataFrame, opts: options.Options) -> Model:
# Find the corresponding indices for train_data, val_data, and test_data in the sorted DataFrame
train_indices = sorted_indices[df.index.isin(train_data.index)]
- # val_indices = sorted_indices[df.index.isin(val_data.index)]
test_indices = sorted_indices[df.index.isin(test_data.index)]
else:
x_train = fp_matrix
@@ -286,33 +252,39 @@ def train_full_ac(df: pd.DataFrame, opts: options.Options) -> Model:
# Set up the model of the AC w.r.t. the input size and the dimension of the bottle neck (z!)
(autoencoder, encoder) = define_ac_model(opts, output_bias=initial_bias)
-
+ callback_list = callbacks.autoencoder_callback(checkpoint_path=save_path, opts=opts)
# Train the autoencoder on the training data
auto_hist = autoencoder.fit(
x_train,
x_train,
- callbacks=callback_list,
+ callbacks=[callback_list],
epochs=opts.aeEpochs,
batch_size=opts.aeBatchSize,
verbose=opts.verbose,
validation_data=(x_test, x_test) if opts.testSize > 0.0 else None,
)
- logging.info(f"Autoencoder weights stored in file: {ac_weights_file}")
# Store the autoencoder training history and plot the metrics
ht.store_and_plot_history(
- base_file_name=os.path.join(opts.outputDir, base_file_name + ".AC"),
+ base_file_name=save_path,
hist=auto_hist,
)
# Save the autoencoder callback model to disk
- save_path = os.path.join(opts.ecModelDir, f"{opts.aeSplitType}_autoencoder")
if opts.testSize > 0.0:
- (callback_autoencoder, callback_encoder) = define_ac_model(opts)
- callback_encoder.save(filepath=save_path)
+ # Re-define autoencoder and encoder using your function
+ callback_autoencoder = load_model(filepath=save_path)
+ _, callback_encoder = define_ac_model(opts)
+ for i, layer in enumerate(callback_encoder.layers):
+ layer.set_weights(callback_autoencoder.layers[i].get_weights())
+
+ # Save the encoder model
+ encoder_save_path = os.path.join(save_path, "encoder_model")
+ callback_encoder.save(filepath=encoder_save_path)
else:
encoder.save(filepath=save_path)
# Return the encoder model of the trained autoencoder
+ autoencoder.summary(print_fn=logging.info)
return encoder, train_indices, test_indices
@@ -341,75 +313,161 @@ def compress_fingerprints(dataframe: pd.DataFrame, encoder: Model) -> pd.DataFra
return dataframe
-def visualize_fingerprints(
- df: pd.DataFrame,
- before_col: str,
- after_col: str,
- train_indices: np.ndarray,
- test_indices: np.ndarray,
- save_as: str,
-):
- # Calculate the number of samples to be taken from each set
- num_samples = 1000
- train_samples = int(num_samples * len(train_indices) / len(df))
- test_samples = num_samples - train_samples
-
- # Assign train and test data points separately
- train_data = df.loc[train_indices]
- test_data = df.loc[test_indices]
-
- # Sample train and test data points
- train_data_sampled = train_data.sample(n=train_samples, random_state=42)
- test_data_sampled = test_data.sample(n=test_samples, random_state=42)
-
- # Concatenate the sampled train and test data
- df_sampled = pd.concat([train_data_sampled, test_data_sampled])
-
- # Convert the boolean values in the after_col column to floats
- df_sampled[after_col] = df_sampled[after_col].apply(
- lambda x: np.array(x, dtype=float)
- )
-
- df_sampled.loc[train_data_sampled.index, "set"] = "train"
- df_sampled.loc[test_data_sampled.index, "set"] = "test"
- # Apply UMAP
- umap_model = umap.UMAP(
- n_neighbors=15, min_dist=0.1, metric="euclidean", random_state=42
- )
- # Filter out the rows with invalid arrays
- umap_results = umap_model.fit_transform(df_sampled[after_col].tolist())
- # Add UMAP results to the DataFrame
- df_sampled["umap_x"] = umap_results[:, 0]
- df_sampled["umap_y"] = umap_results[:, 1]
-
- # Define custom color palette
- palette = {"train": "blue", "test": "red"}
-
- # Create the scatter plot
- sns.set(style="white")
- fig, ax = plt.subplots(figsize=(10, 8))
- split = save_as.split("_", 1)
- part_after_underscore = split[1]
- split_type = part_after_underscore.split(".")[0]
- # Plot the UMAP results
- for label, grp in df_sampled.groupby("set"):
- set_label = label
- color = palette[set_label]
- alpha = (
- 0.09 if set_label == "train" else 0.9
- ) # Set different opacities for train and test
- ax.scatter(
- grp["umap_x"], grp["umap_y"], label=f"{set_label}", c=color, alpha=alpha
- )
-
- # Customize the plot
- ax.set_title(
- f"UMAP visualization of molecular fingerprints using {split_type} split",
- fontsize=14,
- )
- ax.set_xlabel("UMAP 1")
- ax.set_ylabel("UMAP 2")
- ax.legend(title="", loc="upper right")
- sns.despine(ax=ax, offset=10)
- save_path = os.path.join(os.getcwd(), save_as)
- plt.savefig(save_path)
+# def visualize_fingerprints(
+# df: pd.DataFrame,
+# train_indices: np.ndarray,
+# test_indices: np.ndarray,
+# save_as: str,
+# ):
+# """
+# Visualize fingerprints using UMAP and save the visualization to a file.
+#
+# This function takes a Pandas DataFrame containing fingerprint data, calculates
+# the appropriate number of samples based on the size of the dataset, applies UMAP
+# for dimensionality reduction, and saves the resulting visualization.
+#
+# Parameters:
+# - df (pd.DataFrame): A Pandas DataFrame containing fingerprint data.
+# - train_indices (np.ndarray): An array containing indices of training samples.
+# - test_indices (np.ndarray): An array containing indices of test samples.
+# - save_as (str): The filename or path where the UMAP visualization will be saved.
+# Note:
+# - If the DataFrame size exceeds 50,000 rows, the function logs a message and skips
+# the UMAP visualization step.
+# """
+# if len(df) <= 10000:
+# num_samples = len(df)
+# elif len(df) > 50000:
+# logging.info(
+# "Cannot return the UMAP due to the large dataset size. Skipping the function."
+# )
+# return
+# else:
+# num_samples = len(df) // 2
+#
+# after_col = "fpcompressed"
+# # Calculate the number of samples to be taken from each set
+# train_samples = int(num_samples * len(train_indices) / len(df))
+# test_samples = num_samples - train_samples
+#
+# # Assign train and test data points separately
+# train_data = df.loc[train_indices]
+# test_data = df.loc[test_indices]
+#
+# # Sample train and test data points
+# train_data_sampled = train_data.sample(n=train_samples, random_state=42)
+# test_data_sampled = test_data.sample(n=test_samples, random_state=42)
+#
+# # Concatenate the sampled train and test data
+# df_sampled = pd.concat([train_data_sampled, test_data_sampled])
+#
+# # Convert the boolean values in the after_col column to floats
+# df_sampled[after_col] = df_sampled[after_col].apply(
+# lambda x: np.array(x, dtype=float)
+# )
+#
+# df_sampled.loc[train_data_sampled.index, "set"] = "train"
+# df_sampled.loc[test_data_sampled.index, "set"] = "test"
+# # Apply UMAP
+# umap_model = umap.UMAP(
+# n_neighbors=15, min_dist=0.1, metric="euclidean", random_state=42
+# )
+# # Filter out the rows with invalid arrays
+# umap_results = umap_model.fit_transform(df_sampled[after_col].tolist())
+# # Add UMAP results to the DataFrame
+# df_sampled["umap_x"] = umap_results[:, 0]
+# df_sampled["umap_y"] = umap_results[:, 1]
+#
+# # Define custom color palette
+# palette = {"train": "blue", "test": "red"}
+#
+# # Create the scatter plot
+# sns.set(style="white")
+# fig, ax = plt.subplots(figsize=(10, 8))
+# split = save_as.split("_", 1)
+# part_after_underscore = split[1]
+# split_type = part_after_underscore.split(".")[0]
+# # Plot the UMAP results
+# for label, grp in df_sampled.groupby("set"):
+# set_label = label
+# color = palette[set_label]
+# alpha = (
+# 0.09 if set_label == "train" else 0.9
+# ) # Set different opacities for train and test
+# ax.scatter(
+# grp["umap_x"], grp["umap_y"], label=f"{set_label}", c=color, alpha=alpha
+# )
+#
+# # Customize the plot
+# ax.set_title(
+# f"UMAP visualization of molecular fingerprints using {split_type} split",
+# fontsize=14,
+# )
+# ax.set_xlabel("UMAP 1")
+# ax.set_ylabel("UMAP 2")
+# ax.legend(title="", loc="upper right")
+# sns.despine(ax=ax, offset=10)
+# save_path = os.path.join(os.getcwd(), save_as)
+# plt.savefig(save_path)
+import math
+from sklearn.cluster import MiniBatchKMeans
+from sklearn.manifold import TSNE
+from sklearn.metrics import adjusted_rand_score, adjusted_mutual_info_score
+import umap
+import matplotlib.pyplot as plt
+from sklearn.neighbors import NearestNeighbors
+
+def knn_preservation(original_fp, compressed_fp, n_neighbors=5):
+ original_nn = NearestNeighbors(n_neighbors=n_neighbors).fit(original_fp)
+ compressed_nn = NearestNeighbors(n_neighbors=n_neighbors).fit(compressed_fp)
+
+ original_neighbors = original_nn.kneighbors(original_fp, return_distance=False)
+ compressed_neighbors = compressed_nn.kneighbors(compressed_fp, return_distance=False)
+
+ intersection = np.array([len(np.intersect1d(original_neighbors[i], compressed_neighbors[i])) for i in range(len(original_fp))])
+ preservation_score = np.mean(intersection / n_neighbors)
+ return preservation_score
+def knn_preservation(original_fp, compressed_fp, n_neighbors=5):
+ original_nn = NearestNeighbors(n_neighbors=n_neighbors).fit(original_fp)
+ compressed_nn = NearestNeighbors(n_neighbors=n_neighbors).fit(compressed_fp)
+
+ original_neighbors = original_nn.kneighbors(original_fp, return_distance=False)
+ compressed_neighbors = compressed_nn.kneighbors(compressed_fp, return_distance=False)
+
+ intersection = np.array([len(np.intersect1d(original_neighbors[i], compressed_neighbors[i])) for i in range(len(original_fp))])
+ preservation_score = np.mean(intersection / n_neighbors)
+ return preservation_score
+
+def visualize_fingerprints(dataframe, n_clusters=7, save_as=None):
+ # Extract original and compressed fingerprints
+ original_fp = np.array(dataframe['fp'].to_list())
+ compressed_fp = np.array(dataframe['fpcompressed'].to_list())
+
+ original_tsne = TSNE(n_components=2, random_state=42).fit_transform(original_fp)
+ compressed_tsne = TSNE(n_components=2, random_state=42).fit_transform(compressed_fp)
+
+
+ # Cluster using MiniBatchKMeans
+ kmeans = MiniBatchKMeans(n_clusters=n_clusters, random_state=42)
+ original_clusters = kmeans.fit_predict(original_tsne)
+ compressed_clusters = kmeans.fit_predict(compressed_tsne)
+
+ # Calculate metrics
+ ars = adjusted_rand_score(original_clusters, compressed_clusters)
+ ami = adjusted_mutual_info_score(original_clusters, compressed_clusters)
+ knn_preservation_score = knn_preservation(original_fp, compressed_fp)
+
+ # Visualize
+ plt.figure(figsize=(12, 6))
+ plt.subplot(1, 2, 1)
+ plt.scatter(original_tsne[:, 0], original_tsne[:, 1], c=original_clusters, cmap='Spectral', s=5)
+ plt.title('Original TSNE Visualization')
+
+ plt.subplot(1, 2, 2)
+ plt.scatter(compressed_tsne[:, 0], compressed_tsne[:, 1], c=compressed_clusters, cmap='Spectral', s=5)
+ plt.title('Deterministic Autoencoder Latent Space TSNE Visualization')
+ metrics_text = f"ARS: {ars:.2f}\nAMI: {ami:.2f}\nkNN: {knn_preservation_score:.2f}"
+ plt.text(0.95, 0.02, metrics_text, verticalalignment='bottom', horizontalalignment='right', transform=plt.gca().transAxes, color='black', fontsize=9, bbox=dict(facecolor='white', alpha=0.8, edgecolor='black'))
+
+ plt.suptitle('Deterministic Autoencoder Latent Space')
+ plt.savefig(save_as)
\ No newline at end of file
diff --git a/dfpl/callbacks.py b/dfpl/callbacks.py
old mode 100644
new mode 100755
index 6eae7965..8bf157fd
--- a/dfpl/callbacks.py
+++ b/dfpl/callbacks.py
@@ -22,15 +22,25 @@ def autoencoder_callback(checkpoint_path: str, opts: options.Options) -> list:
else:
target = "loss"
# enable this checkpoint to restore the weights of the best performing model
- checkpoint = ModelCheckpoint(
- checkpoint_path,
- monitor=target,
- mode="min",
- verbose=1,
- period=settings.ac_train_check_period,
- save_best_only=True,
- save_weights_only=True,
- )
+ if opts.aeType == "deterministic":
+ checkpoint = ModelCheckpoint(
+ checkpoint_path,
+ monitor=target,
+ mode="min",
+ verbose=1,
+ save_freq="epoch",
+ save_best_only=True,
+ )
+ else:
+ checkpoint = ModelCheckpoint(
+ checkpoint_path,
+ monitor=target,
+ mode="min",
+ verbose=1,
+ save_freq="epoch",
+ save_best_only=True,
+ save_weights_only=True,
+ )
callbacks.append(checkpoint)
# enable early stopping if val_loss is not improving anymore
@@ -43,7 +53,6 @@ def autoencoder_callback(checkpoint_path: str, opts: options.Options) -> list:
restore_best_weights=True,
)
callbacks.append(early_stop)
-
if opts.aeWabTracking and not opts.wabTracking:
callbacks.append(WandbCallback(save_model=False))
return callbacks
@@ -65,7 +74,7 @@ def nn_callback(checkpoint_path: str, opts: options.Options) -> list:
checkpoint = ModelCheckpoint(
checkpoint_path,
verbose=1,
- period=settings.nn_train_check_period,
+ save_freq="epoch",
save_best_only=True,
monitor="val_loss",
mode="min",
diff --git a/dfpl/deepFPlearn-HyperParameterTuning.py b/dfpl/deepFPlearn-HyperParameterTuning.py
old mode 100644
new mode 100755
diff --git a/dfpl/feedforwardNN.py b/dfpl/feedforwardNN.py
old mode 100644
new mode 100755
index e9c88776..3f8ad261
--- a/dfpl/feedforwardNN.py
+++ b/dfpl/feedforwardNN.py
@@ -69,10 +69,16 @@ def define_out_file_names(path_prefix: str, target: str, fold: int = -1) -> tupl
def define_nn_multi_label_model(
input_size: int, output_size: int, opts: options.Options
) -> Model:
+ lr_schedule = optimizers.schedules.ExponentialDecay(
+ opts.aeLearningRate,
+ decay_steps=1000,
+ decay_rate=opts.aeLearningRateDecay,
+ staircase=True,
+ )
if opts.optimizer == "Adam":
- my_optimizer = optimizers.Adam(learning_rate=opts.learningRate)
+ my_optimizer = optimizers.legacy.Adam(learning_rate=lr_schedule)
elif opts.optimizer == "SGD":
- my_optimizer = optimizers.SGD(lr=opts.learningRate, momentum=0.9)
+ my_optimizer = optimizers.legacy.SGD(lr=lr_schedule, momentum=0.9)
else:
logging.error(f"Your selected optimizer is not supported:{opts.optimizer}.")
sys.exit("Unsupported optimizer.")
@@ -132,9 +138,9 @@ def define_nn_model_multi(
decay: float = 0.01,
) -> Model:
if optimizer == "Adam":
- my_optimizer = optimizers.Adam(learning_rate=lr, decay=decay)
+ my_optimizer = optimizers.legacy.Adam(learning_rate=lr, decay=decay)
elif optimizer == "SGD":
- my_optimizer = optimizers.SGD(lr=lr, momentum=0.9, decay=decay)
+ my_optimizer = optimizers.legacy.SGD(lr=lr, momentum=0.9, decay=decay)
else:
my_optimizer = optimizer
@@ -282,7 +288,7 @@ def train_nn_models_multi(df: pd.DataFrame, opts: options.Options) -> None:
"f1_trained",
]
) # F1 scores of predictions
-
+ all_scores_data = []
fold_no = 1
# split the data
@@ -294,6 +300,8 @@ def train_nn_models_multi(df: pd.DataFrame, opts: options.Options) -> None:
model_file_path_weights,
model_file_path_json,
model_hist_path,
+ model_hist_csv_path,
+ model_predict_valset_csv_path,
model_validation,
model_auc_file,
model_auc_file_data,
@@ -351,6 +359,16 @@ def train_nn_models_multi(df: pd.DataFrame, opts: options.Options) -> None:
)
idx = hist.history["val_loss"].index(min(hist.history["val_loss"]))
+ row_data = {
+ "fold_no": fold_no,
+ "loss": hist.history["loss"][idx],
+ "val_loss": hist.history["val_loss"][idx],
+ "acc": hist.history["accuracy"][idx],
+ "val_acc": hist.history["val_accuracy"][idx],
+ "f1_random": scores[0],
+ "f1_trained": scores[1],
+ }
+ all_scores_data.append(row_data)
row_df = pd.DataFrame(
[
[
@@ -375,12 +393,13 @@ def train_nn_models_multi(df: pd.DataFrame, opts: options.Options) -> None:
)
logging.info(row_df)
- all_scores = all_scores.append(row_df, ignore_index=True)
+ all_scores = all_scores_data.append(row_data)#, ignore_index=True)
fold_no += 1
del model
logging.info(all_scores)
+ all_scores = pd.DataFrame(all_scores_data)
# finalize model
# 1. provide best performing fold variant
@@ -389,11 +408,11 @@ def train_nn_models_multi(df: pd.DataFrame, opts: options.Options) -> None:
fold_no = all_scores.iloc[idx2]["fold_no"]
model_name = (
- "multi_compressed-" + str(opts.compressFeatures) + ".Fold-" + str(fold_no)
+ "multi_compressed-" + str(opts.compressFeatures) + ".Fold-" + str(int(fold_no))
)
checkpoint_path = opts.outputDir + "/" + model_name + ".checkpoint.model.hdf5"
best_model_file = checkpoint_path.replace(
- "Fold-" + str(fold_no) + ".checkpoint.", "best.FNN-"
+ "Fold-" + str(int(fold_no)) + ".checkpoint.", "best.FNN-"
)
file = re.sub(
diff --git a/dfpl/fingerprint.py b/dfpl/fingerprint.py
old mode 100644
new mode 100755
diff --git a/dfpl/history.py b/dfpl/history.py
old mode 100644
new mode 100755
diff --git a/dfpl/options.py b/dfpl/options.py
old mode 100644
new mode 100755
index 6d84dbc4..347e6058
--- a/dfpl/options.py
+++ b/dfpl/options.py
@@ -3,12 +3,13 @@
import argparse
from dataclasses import dataclass
from pathlib import Path
+from typing import Optional, Literal, List
import jsonpickle
import torch
-from chemprop.args import TrainArgs
+from chemprop.args import TrainArgs, PredictArgs, InterpretArgs
-from dfpl.utils import makePathAbsolute
+from dfpl.utils import parseCmdArgs
@dataclass
@@ -17,51 +18,52 @@ class Options:
Dataclass for all options necessary for training the neural nets
"""
- configFile: str = "./example/train.json"
- inputFile: str = "/deepFPlearn/CMPNN/data/tox21.csv"
- outputDir: str = "."
- outputFile: str = ""
- ecWeightsFile: str = "AE.encoder.weights.hdf5"
- ecModelDir: str = "AE_encoder"
- fnnModelDir: str = "modeltraining"
+ configFile: str = None
+ inputFile: str = "tests/data/smiles.csv"
+ outputDir: str = "example/results_train/" # changes according to mode
+ outputFile: str = "results.csv"
+ ecWeightsFile: str = ""
+ ecModelDir: str = "example/results_train/AE_encoder/"
+ fnnModelDir: str = "example/results_train/AR_saved_model/"
type: str = "smiles"
fpType: str = "topological" # also "MACCS", "atompairs"
- epochs: int = 512
+ epochs: int = 100
fpSize: int = 2048
encFPSize: int = 256
- kFolds: int = 0
+ kFolds: int = 1
testSize: float = 0.2
enableMultiLabel: bool = False
- verbose: int = 0
- trainAC: bool = True # if set to False, an AC weight file must be provided!
+ verbose: int = 2
+ trainAC: bool = False
trainFNN: bool = True
- compressFeatures: bool = True
- sampleFractionOnes: float = 0.5 # Only used when value is in [0,1]
+ finetuneEncoder: bool = False
+ compressFeatures: bool = False
+ sampleFractionOnes: float = 0.5
sampleDown: bool = False
split_type: str = "random"
aeSplitType: str = "random"
aeType: str = "deterministic"
- aeEpochs: int = 3000
+ aeEpochs: int = 100
aeBatchSize: int = 512
aeLearningRate: float = 0.001
- aeLearningRateDecay: float = 0.01
- aeActivationFunction: str = "relu"
+ aeLearningRateDecay: float = 0.96
+ aeActivationFunction: str = "selu"
aeOptimizer: str = "Adam"
fnnType: str = "FNN"
batchSize: int = 128
optimizer: str = "Adam"
learningRate: float = 0.001
+ learningRateDecay: float = 0.96
lossFunction: str = "bce"
activationFunction: str = "relu"
l2reg: float = 0.001
dropout: float = 0.2
threshold: float = 0.5
- gpu: str = ""
- snnDepth = 8
- snnWidth = 50
- aeWabTracking: str = "" # Wand & Biases autoencoder tracking
- wabTracking: str = "" # Wand & Biases FNN tracking
- wabTarget: str = "ER" # Wand & Biases target used for showing training progress
+ visualizeLatent: bool = False # only if autoencoder is trained or loaded
+ gpu: int = None
+ aeWabTracking: bool = False # Wand & Biases autoencoder tracking
+ wabTracking: bool = False # Wand & Biases FNN tracking
+ wabTarget: str = "AR" # Wand & Biases target used for showing training progress
def saveToFile(self, file: str) -> None:
"""
@@ -72,42 +74,8 @@ def saveToFile(self, file: str) -> None:
f.write(jsonpickle.encode(self))
@classmethod
- def fromJson(cls, file: str) -> Options:
- """
- Create an instance from a JSON file
- """
- jsonFile = Path(file)
- if jsonFile.exists() and jsonFile.is_file():
- with jsonFile.open() as f:
- content = f.read()
- return jsonpickle.decode(content)
- raise ValueError("JSON file does not exist or is not readable")
-
- @classmethod
- def fromCmdArgs(cls, args: argparse.Namespace) -> Options:
- """
- Creates Options instance from cmdline arguments.
-
- If a training file (JSON) is provided, the values from that file are used.
- However, additional commandline arguments will be preferred. If, e.g., "fpSize" is specified both in the
- JSON file and on the commandline, then the value of the commandline argument will be used.
- """
- result = Options()
- if "configFile" in vars(args).keys():
- jsonFile = Path(makePathAbsolute(args.configFile))
- if jsonFile.exists() and jsonFile.is_file():
- with jsonFile.open() as f:
- content = f.read()
- result = jsonpickle.decode(content)
- else:
- raise ValueError("Could not find JSON input file")
-
- for key, value in vars(args).items():
- # The args dict will contain a "method" key from the subparser.
- # We don't use this.
- if key != "method":
- result.__setattr__(key, value)
- return result
+ def fromCmdArgs(cls, args: argparse.Namespace) -> "Options":
+ return parseCmdArgs(cls, args)
@dataclass
@@ -118,8 +86,8 @@ class GnnOptions(TrainArgs):
total_epochs: int = 30
save: bool = True
- configFile: str = "./example/traingnn.json"
- data_path: str = "./example/data/tox21.csv"
+ configFile: str = ""
+ data_path: str = ""
use_compound_names: bool = False
save_dir: str = ""
no_cache: bool = False
@@ -129,42 +97,118 @@ class GnnOptions(TrainArgs):
num_lrs: int = 2
minimize_score: bool = False
num_tasks: int = 12
- preds_path: str = "./tox21dmpnn.csv"
+ preds_path: str = ""
test_path: str = ""
- save_preds: bool = True
+ save_preds: bool = False
+ calibration_method: str = ""
+ uncertainty_method: str = ""
+ calibration_path: str = ""
+ evaluation_methods: str = ""
+ evaluation_scores_path: str = ""
+ wabTracking: bool = False
+ split_sizes: List[float] = None
+ show_individual_scores: bool = False
+ # save_smiles_splits: bool = False
@classmethod
- def fromCmdArgs(cls, args: argparse.Namespace) -> GnnOptions:
- """
- Creates Options instance from cmdline arguments.
+ def fromCmdArgs(cls, args: argparse.Namespace, json_config: Optional[dict] = None):
+ # Initialize with JSON config if provided
+ if json_config:
+ opts = cls(**json_config)
+ else:
+ opts = cls()
- If a training file (JSON) is provided, the values from that file are used.
- However, additional commandline arguments will be preferred. If, e.g., "fpSize" is specified both in the
- JSON file and on the commandline, then the value of the commandline argument will be used.
- """
- result = GnnOptions()
- if "configFile" in vars(args).keys():
- jsonFile = Path(makePathAbsolute(args.configFile))
- if jsonFile.exists() and jsonFile.is_file():
- with jsonFile.open() as f:
- content = f.read()
- result = jsonpickle.decode(content)
- else:
- raise ValueError("Could not find JSON input file")
-
- return result
+ # Update with command-line arguments
+ for key, value in vars(args).items():
+ if value is not None:
+ setattr(opts, key, value)
+
+ return opts
+
+class PredictGnnOptions(PredictArgs):
+ """
+ Dataclass to hold all options used for training the graph models
+ """
+
+ configFile: str = "./example/predictgnn.json"
+ calibration_atom_descriptors_path: str = None
+ calibration_features_path: str = None
+ calibration_interval_percentile: float = 95
+ calibration_method: Literal[
+ "zscaling",
+ "tscaling",
+ "zelikman_interval",
+ "mve_weighting",
+ "platt",
+ "isotonic",
+ ] = None
+ calibration_path: str = None
+ calibration_phase_features_path: str = None
+ drop_extra_columns: bool = False
+ dropout_sampling_size: int = 10
+ evaluation_methods: List[str] = None
+ evaluation_scores_path: str = None
+ # no_features_scaling: bool = True
+ individual_ensemble_predictions: bool = False
+ preds_path: str = None
+ regression_calibrator_metric: Literal["stdev", "interval"] = None
+ test_path: str = None
+ uncertainty_dropout_p: float = 0.1
+ uncertainty_method: Literal[
+ "mve",
+ "ensemble",
+ "evidential_epistemic",
+ "evidential_aleatoric",
+ "evidential_total",
+ "classification",
+ "dropout",
+ ] = None
@classmethod
- def fromJson(cls, file: str) -> GnnOptions:
- """
- Create an instance from a JSON file
- """
- jsonFile = Path(file)
- if jsonFile.exists() and jsonFile.is_file():
- with jsonFile.open() as f:
- content = f.read()
- return jsonpickle.decode(content)
- raise ValueError("JSON file does not exist or is not readable")
+ def fromCmdArgs(cls, args: argparse.Namespace, json_config: Optional[dict] = None):
+ # Initialize with JSON config if provided
+ if json_config:
+ opts = cls(**json_config)
+ else:
+ opts = cls()
+
+ # Update with command-line arguments
+ for key, value in vars(args).items():
+ if value is not None:
+ setattr(opts, key, value)
+
+ return opts
+
+
+class InterpretGNNoptions(InterpretArgs):
+ """
+ Dataclass to hold all options used for training the graph models
+ """
+
+ configFile: str = "./example/interpret.json"
+ data_path: str = "./example/data/smiles.csv"
+ batch_size: int = 500
+ c_puct: float = 10.0
+ max_atoms: int = 20
+ min_atoms: int = 8
+ prop_delta: float = 0.5
+ property_id: List[int] = None
+ rollout: int = 20
+
+ @classmethod
+ def fromCmdArgs(cls, args: argparse.Namespace, json_config: Optional[dict] = None):
+ # Initialize with JSON config if provided
+ if json_config:
+ opts = cls(**json_config)
+ else:
+ opts = cls()
+
+ # Update with command-line arguments
+ for key, value in vars(args).items():
+ if value is not None:
+ setattr(opts, key, value)
+
+ return opts
def createCommandlineParser() -> argparse.ArgumentParser:
@@ -186,6 +230,12 @@ def createCommandlineParser() -> argparse.ArgumentParser:
parser_predict_gnn.set_defaults(method="predictgnn")
parsePredictGnn(parser_predict_gnn)
+ parser_interpret_gnn = subparsers.add_parser(
+ "interpretgnn", help="Interpret your GNN models"
+ )
+ parser_interpret_gnn.set_defaults(method="interpretgnn")
+ parseInterpretGnn(parser_interpret_gnn)
+
parser_train = subparsers.add_parser(
"train", help="Train new models with your data"
)
@@ -225,7 +275,7 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
metavar="FILE",
type=str,
help="Input JSON file that contains all information for training/predicting.",
- default=argparse.SUPPRESS,
+ default="example/train.json",
)
general_args.add_argument(
"-i",
@@ -234,7 +284,7 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
help="The file containing the data for training in "
"comma separated CSV format.The first column should be smiles.",
- default=argparse.SUPPRESS,
+ default="tests/data/smiles.csv",
)
general_args.add_argument(
"-o",
@@ -243,8 +293,10 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
help="Prefix of output file name. Trained model and "
"respective stats will be returned in this directory.",
- default=argparse.SUPPRESS,
+ default="example/results_train/",
)
+
+ # TODO CHECK WHAT IS TYPE DOING?
general_args.add_argument(
"-t",
"--type",
@@ -252,7 +304,7 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["fp", "smiles"],
help="Type of the chemical representation. Choices: 'fp', 'smiles'.",
- default=argparse.SUPPRESS,
+ default="fp",
)
general_args.add_argument(
"-thr",
@@ -260,47 +312,41 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=float,
metavar="FLOAT",
help="Threshold for binary classification.",
- default=argparse.SUPPRESS,
+ default=0.5,
)
general_args.add_argument(
"-gpu",
"--gpu",
metavar="INT",
type=int,
- help="Select which gpu to use. If not available, leave empty.",
- default=argparse.SUPPRESS,
+ help="Select which gpu to use by index. If not available, leave empty",
+ default=None,
)
general_args.add_argument(
- "-k",
"--fpType",
metavar="STR",
type=str,
- choices=["topological", "MACCS"], # , 'atompairs', 'torsions'],
- help="The type of fingerprint to be generated/used in input file.",
- default=argparse.SUPPRESS,
+ choices=["topological", "MACCS"],
+ help="The type of fingerprint to be generated/used in input file. MACCS or topological are available.",
+ default="topological",
)
general_args.add_argument(
- "-s",
"--fpSize",
type=int,
- help="Size of fingerprint that should be generated.",
- default=argparse.SUPPRESS,
+ help="Length of the fingerprint that should be generated.",
+ default=2048,
)
general_args.add_argument(
- "-c",
"--compressFeatures",
- metavar="BOOL",
- type=bool,
- help="Should the fingerprints be compressed or not. Activates the autoencoder. ",
- default=argparse.SUPPRESS,
+ action="store_true",
+ help="Should the fingerprints be compressed or not. Needs a path of a trained autoencoder or needs the trainAC also set to True.",
+ default=False,
)
general_args.add_argument(
- "-m",
"--enableMultiLabel",
- metavar="BOOL",
- type=bool,
+ action="store_true",
help="Train multi-label classification model in addition to the individual models.",
- default=argparse.SUPPRESS,
+ default=False,
)
# Autoencoder Configuration
autoencoder_args.add_argument(
@@ -309,14 +355,14 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
metavar="FILE",
help="The .hdf5 file of a trained encoder",
- default=argparse.SUPPRESS,
+ default="",
)
autoencoder_args.add_argument(
"--ecModelDir",
type=str,
metavar="DIR",
help="The directory where the full model of the encoder will be saved",
- default=argparse.SUPPRESS,
+ default="example/results_train/AE_encoder/",
)
autoencoder_args.add_argument(
"--aeType",
@@ -324,21 +370,21 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["variational", "deterministic"],
help="Autoencoder type, variational or deterministic.",
- default=argparse.SUPPRESS,
+ default="deterministic",
)
autoencoder_args.add_argument(
"--aeEpochs",
metavar="INT",
type=int,
help="Number of epochs for autoencoder training.",
- default=argparse.SUPPRESS,
+ default=100,
)
autoencoder_args.add_argument(
"--aeBatchSize",
metavar="INT",
type=int,
help="Batch size in autoencoder training.",
- default=argparse.SUPPRESS,
+ default=512,
)
autoencoder_args.add_argument(
"--aeActivationFunction",
@@ -346,21 +392,21 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["relu", "selu"],
help="The activation function for the hidden layers in the autoencoder.",
- default=argparse.SUPPRESS,
+ default="relu",
)
autoencoder_args.add_argument(
"--aeLearningRate",
metavar="FLOAT",
type=float,
help="Learning rate for autoencoder training.",
- default=argparse.SUPPRESS,
+ default=0.001,
)
autoencoder_args.add_argument(
"--aeLearningRateDecay",
metavar="FLOAT",
type=float,
help="Learning rate decay for autoencoder training.",
- default=argparse.SUPPRESS,
+ default=0.96,
)
autoencoder_args.add_argument(
"--aeSplitType",
@@ -368,7 +414,7 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["scaffold_balanced", "random", "molecular_weight"],
help="Set how the data is going to be split for the autoencoder",
- default=argparse.SUPPRESS,
+ default="random",
)
autoencoder_args.add_argument(
"-d",
@@ -376,7 +422,13 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
metavar="INT",
type=int,
help="Size of encoded fingerprint (z-layer of autoencoder).",
- default=argparse.SUPPRESS,
+ default=256,
+ )
+ autoencoder_args.add_argument(
+ "--visualizeLatent",
+ action="store_true",
+ help="UMAP the latent space for exploration",
+ default=False,
)
# Training Configuration
training_args.add_argument(
@@ -385,15 +437,14 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["scaffold_balanced", "random", "molecular_weight"],
help="Set how the data is going to be split for the feedforward neural network",
- default=argparse.SUPPRESS,
+ default="random",
)
training_args.add_argument(
- "-l",
"--testSize",
metavar="FLOAT",
type=float,
help="Fraction of the dataset that should be used for testing. Value in [0,1].",
- default=argparse.SUPPRESS,
+ default=0.2,
)
training_args.add_argument(
"-K",
@@ -401,7 +452,7 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
metavar="INT",
type=int,
help="K that is used for K-fold cross-validation in the training procedure.",
- default=argparse.SUPPRESS,
+ default=1,
)
training_args.add_argument(
"-v",
@@ -411,21 +462,19 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
choices=[0, 1, 2],
help="Verbosity level. O: No additional output, "
+ "1: Some additional output, 2: full additional output",
- default=argparse.SUPPRESS,
+ default=2,
)
training_args.add_argument(
"--trainAC",
- metavar="BOOL",
- type=bool,
+ action="store_true",
help="Choose to train or not, the autoencoder based on the input file",
- default=argparse.SUPPRESS,
+ default=False,
)
training_args.add_argument(
"--trainFNN",
- metavar="BOOL",
- type=bool,
- help="Train the feedforward network either with provided weights.",
- default=argparse.SUPPRESS,
+ action="store_false",
+ help="When called it deactivates the training.",
+ default=True,
)
training_args.add_argument(
"--sampleFractionOnes",
@@ -433,14 +482,14 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=float,
help="This is the fraction of positive target associations (1s) in comparison to the majority class(0s)."
"only works if --sampleDown is enabled",
- default=argparse.SUPPRESS,
+ default=0.5,
)
training_args.add_argument(
"--sampleDown",
metavar="BOOL",
type=bool,
help="Enable automatic down sampling of the 0 valued samples.",
- default=argparse.SUPPRESS,
+ default=False,
)
training_args.add_argument(
"-e",
@@ -448,52 +497,66 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
metavar="INT",
type=int,
help="Number of epochs that should be used for the FNN training",
- default=argparse.SUPPRESS,
+ default=100,
)
-
+ training_args.add_argument(
+ "--finetuneEncoder",
+ action="store_true",
+ help="Finetune the encoder with the FNN",
+ default=False,
+ )
+ # TODO CHECK IF ALL LOSSES MAKE SENSE HERE
training_args.add_argument(
"--lossFunction",
metavar="STRING",
type=str,
choices=["mse", "bce", "focal"],
help="Loss function to use during training. mse - mean squared error, bce - binary cross entropy.",
- default=argparse.SUPPRESS,
+ default="bce",
)
+ # TODO DO I NEED ALL ARGUMENTS TO BE USER SPECIFIED? WHAT DOES THE USER KNOW ABOUT OPTIMIZERS?
training_args.add_argument(
"--optimizer",
metavar="STRING",
type=str,
choices=["Adam", "SGD"],
help='Optimizer to use for backpropagation in the FNN. Possible values: "Adam", "SGD"',
- default=argparse.SUPPRESS,
+ default="Adam",
)
training_args.add_argument(
"--batchSize",
metavar="INT",
type=int,
help="Batch size in FNN training.",
- default=argparse.SUPPRESS,
+ default=128,
)
training_args.add_argument(
"--l2reg",
metavar="FLOAT",
type=float,
help="Value for l2 kernel regularizer.",
- default=argparse.SUPPRESS,
+ default=0.001,
)
training_args.add_argument(
"--dropout",
metavar="FLOAT",
type=float,
help="The fraction of data that is dropped out in each dropout layer.",
- default=argparse.SUPPRESS,
+ default=0.2,
)
training_args.add_argument(
"--learningRate",
metavar="FLOAT",
type=float,
help="Learning rate size in FNN training.",
- default=argparse.SUPPRESS,
+ default=0.000022,
+ )
+ training_args.add_argument(
+ "--learningRateDecay",
+ metavar="FLOAT",
+ type=float,
+ help="Learning rate size in FNN training.",
+ default=0.96,
)
training_args.add_argument(
"--activationFunction",
@@ -501,7 +564,7 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["relu", "selu"],
help="The activation function for hidden layers in the FNN.",
- default=argparse.SUPPRESS,
+ default="relu",
)
# Tracking Configuration
tracking_args.add_argument(
@@ -509,14 +572,14 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
metavar="BOOL",
type=bool,
help="Track autoencoder performance via Weights & Biases, see https://wandb.ai.",
- default=argparse.SUPPRESS,
+ default=False,
)
tracking_args.add_argument(
"--wabTracking",
metavar="BOOL",
type=bool,
help="Track FNN performance via Weights & Biases, see https://wandb.ai.",
- default=argparse.SUPPRESS,
+ default=False,
)
tracking_args.add_argument(
"--wabTarget",
@@ -524,7 +587,112 @@ def parseInputTrain(parser: argparse.ArgumentParser) -> None:
type=str,
choices=["AR", "ER", "ED", "GR", "TR", "PPARg", "Aromatase"],
help="Which target to use for tracking performance via Weights & Biases, see https://wandb.ai.",
- default=argparse.SUPPRESS,
+ default="AR",
+ )
+
+
+def parseInputPredict(parser: argparse.ArgumentParser) -> None:
+ """
+ Parse the input arguments.
+
+ :return: A namespace object built up from attributes parsed out of the cmd line.
+ """
+
+ general_args = parser.add_argument_group("General Configuration")
+ files_args = parser.add_argument_group("Files")
+ files_args.add_argument(
+ "-f",
+ "--configFile",
+ metavar="FILE",
+ type=str,
+ help="Input JSON file that contains all information for training/predicting.",
+ )
+ files_args.add_argument(
+ "-i",
+ "--inputFile",
+ metavar="FILE",
+ type=str,
+ help="The file containing the data for the prediction in (unquoted) "
+ "comma separated CSV format. The column named 'smiles' or 'fp'"
+ "contains the field to be predicted. Please adjust the type "
+ "that should be predicted (fp or smile) with -t option appropriately."
+ "An optional column 'id' is used to assign the outcomes to the"
+ "original identifiers. If this column is missing, the results are"
+ "numbered in the order of their appearance in the input file."
+ "A header is expected and respective column names are used.",
+ default="tests/data/smiles.csv",
+ )
+ files_args.add_argument(
+ "-o",
+ "--outputDir",
+ metavar="DIR",
+ type=str,
+ help="Prefix of output directory. It will contain a log file and the file specified"
+ "with --outputFile.",
+ default="example/results_predict/",
+ )
+ files_args.add_argument(
+ "--outputFile",
+ metavar="FILE",
+ type=str,
+ help="Output .CSV file name which will contain one prediction per input line. "
+ "Default: prefix of input file name.",
+ default="results.csv",
+ )
+ # TODO AGAIN THIS TRASH HERE? CAN WE EVEN PROCESS SMILES?
+ general_args.add_argument(
+ "-t",
+ "--type",
+ metavar="STR",
+ type=str,
+ choices=["fp", "smiles"],
+ help="Type of the chemical representation. Choices: 'fp', 'smiles'.",
+ default="fp",
+ )
+ general_args.add_argument(
+ "-k",
+ "--fpType",
+ metavar="STR",
+ type=str,
+ choices=["topological", "MACCS"],
+ help="The type of fingerprint to be generated/used in input file. Should be the same as the type of the fps that the model was trained upon.",
+ default="topological",
+ )
+ files_args.add_argument(
+ "--ecModelDir",
+ type=str,
+ metavar="DIR",
+ help="The directory where the full model of the encoder will be saved (if trainAE=True) or "
+ "loaded from (if trainAE=False). Provide a full path here.",
+ default="",
+ )
+ files_args.add_argument(
+ "--ecWeightsFile",
+ type=str,
+ metavar="STR",
+ help="The file where the full model of the encoder will be loaded from, to compress the fingerprints. Provide a full path here.",
+ default="",
+ )
+ files_args.add_argument(
+ "--fnnModelDir",
+ type=str,
+ metavar="DIR",
+ help="The directory where the full model of the fnn is loaded from. "
+ "Provide a full path here.",
+ default="example/results_train/AR_saved_model",
+ )
+ general_args.add_argument(
+ "-c", "--compressFeatures", action="store_true", default=False
+ )
+ (
+ general_args.add_argument(
+ "--aeType",
+ metavar="STRING",
+ type=str,
+ choices=["variational", "deterministic"],
+ help="Autoencoder type, variational or deterministic.",
+ default="deterministic",
+ )
)
@@ -534,21 +702,62 @@ def parseTrainGnn(parser: argparse.ArgumentParser) -> None:
files_args = parser.add_argument_group("Files")
model_args = parser.add_argument_group("Model arguments")
training_args = parser.add_argument_group("Training Configuration")
+ uncertainty_args = parser.add_argument_group("Uncertainty Configuration")
+ uncertainty_args.add_argument(
+ "--uncertainty_method",
+ type=str,
+ metavar="STRING",
+ choices=[
+ "mve",
+ "ensemble",
+ "evidential_epistemic",
+ "evidential_aleatoric",
+ "evidential_total",
+ "classification",
+ "dropout",
+ "dirichlet",
+ ],
+ help="Method to use for uncertainty estimation",
+ default="none",
+ )
+ # Uncertainty arguments
+ uncertainty_args.add_argument(
+ "--calibration_method",
+ type=str,
+ metavar="STRING",
+ choices=[
+ "zscaling",
+ "tscaling",
+ "zelikman_interval",
+ "mve_weighting",
+ "platt",
+ "isotonic",
+ ],
+ help="Method to use for calibration",
+ default="none",
+ )
+ uncertainty_args.add_argument(
+ "--calibration_path",
+ type=str,
+ metavar="FILE",
+ help="Path to file with calibration data",
+ )
# General arguments
general_args.add_argument("--split_key_molecule", type=int)
general_args.add_argument("--pytorch_seed", type=int)
general_args.add_argument("--cache_cutoff", type=float)
- general_args.add_argument("--save_preds", type=bool)
+ general_args.add_argument("--save_preds", action="store_true", default=False)
+ general_args.add_argument("--wabTracking", action="store_true", default=False)
general_args.add_argument(
"--cuda", action="store_true", default=False, help="Turn on cuda"
)
- general_args.add_argument(
- "--save_smiles_splits",
- action="store_true",
- default=False,
- help="Save smiles for each train/val/test splits for prediction convenience later",
- )
+ # general_args.add_argument(
+ # "--save_smiles_splits",
+ # action="store_true",
+ # default=False,
+ # help="Save smiles for each train/val/test splits for prediction convenience later",
+ # )
general_args.add_argument(
"--test",
action="store_true",
@@ -575,9 +784,6 @@ def parseTrainGnn(parser: argparse.ArgumentParser) -> None:
default=10,
help="The number of batches between each logging of the training loss",
)
- general_args.add_argument(
- "--no_cuda", action="store_true", default=True, help="Turn off cuda"
- )
general_args.add_argument(
"--no_cache",
action="store_true",
@@ -593,13 +799,6 @@ def parseTrainGnn(parser: argparse.ArgumentParser) -> None:
type=str,
help="Input JSON file that contains all information for training/predicting.",
)
- files_args.add_argument(
- "--config_path",
- type=str,
- metavar="FILE",
- help="Path to a .json file containing arguments. Any arguments present in the config"
- "file will override arguments specified via the command line or by the defaults.",
- )
files_args.add_argument(
"--save_dir",
type=str,
@@ -917,7 +1116,6 @@ def parseTrainGnn(parser: argparse.ArgumentParser) -> None:
model_args.add_argument(
"--show_individual_scores",
action="store_true",
- default=True,
help="Show all scores for individual targets, not just average, at the end",
)
model_args.add_argument("--aggregation", choices=["mean", "sum", "norm"])
@@ -1034,141 +1232,151 @@ def parseTrainGnn(parser: argparse.ArgumentParser) -> None:
)
-def parseInputPredict(parser: argparse.ArgumentParser) -> None:
- """
- Parse the input arguments.
-
- :return: A namespace object built up from attributes parsed out of the cmd line.
- """
-
+def parsePredictGnn(parser: argparse.ArgumentParser) -> None:
general_args = parser.add_argument_group("General Configuration")
files_args = parser.add_argument_group("Files")
+ uncertainty_args = parser.add_argument_group("Uncertainty Configuration")
+
+ general_args.add_argument(
+ "--checkpoint_path",
+ type=str,
+ metavar="FILE",
+ help="Path to model checkpoint (.pt file)",
+ )
+ # general_args.add_argument(
+ # "--no_features_scaling",
+ # action="store_true",
+ # help="Turn on scaling of features",
+ # )
files_args.add_argument(
"-f",
"--configFile",
- metavar="FILE",
type=str,
- help="Input JSON file that contains all information for training/predicting.",
- default=argparse.SUPPRESS,
- )
- files_args.add_argument(
- "-i",
- "--inputFile",
metavar="FILE",
- type=str,
- help="The file containing the data for the prediction in (unquoted) "
- "comma separated CSV format. The column named 'smiles' or 'fp'"
- "contains the field to be predicted. Please adjust the type "
- "that should be predicted (fp or smile) with -t option appropriately."
- "An optional column 'id' is used to assign the outcomes to the"
- "original identifiers. If this column is missing, the results are"
- "numbered in the order of their appearance in the input file."
- "A header is expected and respective column names are used.",
- default=argparse.SUPPRESS,
+ help="Path to a .json file containing arguments. Any arguments present in the config"
+ "file will override arguments specified via the command line or by the defaults.",
)
files_args.add_argument(
- "-o",
- "--outputDir",
- metavar="DIR",
+ "--test_path",
type=str,
- help="Prefix of output directory. It will contain a log file and the file specified"
- "with --outputFile.",
- default=argparse.SUPPRESS,
+ help="Path to CSV file containing testing data for which predictions will be made.",
)
files_args.add_argument(
- "--outputFile",
- metavar="FILE",
+ "--preds_path",
type=str,
- help="Output .CSV file name which will contain one prediction per input line. "
- "Default: prefix of input file name.",
- default=argparse.SUPPRESS,
+ help="Path to CSV or PICKLE file where predictions will be saved.",
)
- general_args.add_argument(
- "-t",
- "--type",
- metavar="STR",
+ files_args.add_argument(
+ "--calibration_path",
type=str,
- choices=["fp", "smiles"],
- help="Type of the chemical representation. Choices: 'fp', 'smiles'.",
- default=argparse.SUPPRESS,
+ help="Path to data file to be used for uncertainty calibration.",
)
- general_args.add_argument(
- "-k",
- "--fpType",
- metavar="STR",
+ files_args.add_argument(
+ "--calibration_features_path",
type=str,
- choices=["topological", "MACCS"], # , 'atompairs', 'torsions'],
- help="The type of fingerprint to be generated/used in input file.",
- default=argparse.SUPPRESS,
+ nargs="+",
+ help="Path to features data to be used with the uncertainty calibration dataset.",
)
+ files_args.add_argument("--calibration_phase_features_path", type=str, help="")
files_args.add_argument(
- "--ecModelDir",
+ "--calibration_atom_descriptors_path",
type=str,
- metavar="DIR",
- help="The directory where the full model of the encoder will be saved (if trainAE=True) or "
- "loaded from (if trainAE=False). Provide a full path here.",
- default=argparse.SUPPRESS,
+ help="Path to the extra atom descriptors.",
)
files_args.add_argument(
- "--fnnModelDir",
+ "--calibration_bond_descriptors_path",
type=str,
- metavar="DIR",
- help="The directory where the full model of the fnn is loaded from. "
- "Provide a full path here.",
- default=argparse.SUPPRESS,
+ help="Path to the extra bond descriptors that will be used as bond features to featurize a given molecule.",
)
+ general_args.add_argument(
+ "--drop_extra_columns",
+ action="store_true",
+ help="Whether to drop all columns from the test data file besides the SMILES columns and the new prediction columns.",
+ )
-def parsePredictGnn(parser: argparse.ArgumentParser) -> None:
- general_args = parser.add_argument_group("General Configuration")
- data_args = parser.add_argument_group("Data Configuration")
- files_args = parser.add_argument_group("Files")
- training_args = parser.add_argument_group("Training Configuration")
- files_args.add_argument(
- "-f",
- "--configFile",
- metavar="FILE",
+ uncertainty_args.add_argument(
+ "--uncertainty_method",
type=str,
- help="Input JSON file that contains all information for training/predicting.",
- default=argparse.SUPPRESS,
+ choices=[
+ "mve",
+ "ensemble",
+ "evidential_epistemic",
+ "evidential_aleatoric",
+ "evidential_total",
+ "classification",
+ "dropout",
+ "spectra_roundrobin",
+ "dirichlet",
+ ],
+ help="The method of calculating uncertainty.",
)
- general_args.add_argument(
- "--gpu",
- type=int,
- metavar="INT",
- choices=list(range(torch.cuda.device_count())),
- help="Which GPU to use",
+ uncertainty_args.add_argument(
+ "--calibration_method",
+ type=str,
+ nargs="+",
+ choices=[
+ "zscaling",
+ "tscaling",
+ "zelikman_interval",
+ "mve_weighting",
+ "platt",
+ "isotonic",
+ ],
+ help="Methods used for calibrating the uncertainty calculated with uncertainty method.",
)
- general_args.add_argument(
- "--no_cuda", action="store_true", default=False, help="Turn off cuda"
+ uncertainty_args.add_argument(
+ "--individual_ensemble_predictions",
+ action="store_true",
+ default=False,
+ help="Whether to save individual ensemble predictions.",
)
- general_args.add_argument(
- "--num_workers",
+ uncertainty_args.add_argument(
+ "--evaluation_methods",
+ type=str,
+ nargs="+",
+ help="The methods used for evaluating the uncertainty performance if the test data provided includes targets. Available methods are [nll, miscalibration_area, ence, spearman] or any available classification or multiclass metric.",
+ )
+ uncertainty_args.add_argument(
+ "--evaluation_scores_path",
+ type=str,
+ help="Location to save the results of uncertainty evaluations.",
+ )
+ uncertainty_args.add_argument(
+ "--uncertainty_dropout_p",
+ type=float,
+ default=0.1,
+ help="The probability to use for Monte Carlo dropout uncertainty estimation.",
+ )
+ uncertainty_args.add_argument(
+ "--dropout_sampling_size",
type=int,
- metavar="INT",
- help="Number of workers for the parallel data loading 0 means sequential",
+ default=10,
+ help="The number of samples to use for Monte Carlo dropout uncertainty estimation. Distinct from the dropout used during training.",
)
- general_args.add_argument(
- "--no_cache",
- type=bool,
- metavar="BOOL",
- default=False,
- help="Turn off caching mol2graph computation",
+ uncertainty_args.add_argument(
+ "--calibration_interval_percentile",
+ type=float,
+ default=95,
+ help="Sets the percentile used in the calibration methods. Must be in the range (1,100).",
)
- general_args.add_argument(
- "--no_cache_mol",
- type=bool,
- metavar="BOOL",
- default=False,
- help="Whether to not cache the RDKit molecule for each SMILES string to reduce memory\
- usage cached by default",
+ uncertainty_args.add_argument(
+ "--regression_calibrator_metric",
+ type=str,
+ choices=["stdev", "interval"],
+ help="Regression calibrators can output either a stdev or an inverval.",
)
- general_args.add_argument(
- "--empty_cache",
- type=bool,
- metavar="BOOL",
- help="Whether to empty all caches before training or predicting. This is necessary if\
- multiple jobs are run within a single script and the atom or bond features change",
+
+
+def parseInterpretGnn(parser: argparse.ArgumentParser) -> None:
+ files_args = parser.add_argument_group("Files")
+ interpret_args = parser.add_argument_group("Interpretation Configuration")
+ files_args.add_argument(
+ "-f",
+ "--configFile",
+ metavar="FILE",
+ type=str,
+ help="Input JSON file that contains all information for interpretation.",
)
files_args.add_argument(
"--preds_path",
@@ -1191,89 +1399,44 @@ def parsePredictGnn(parser: argparse.ArgumentParser) -> None:
metavar="DIR",
help="Path to model checkpoint (.pt file)",
)
- files_args.add_argument(
- "--checkpoint_paths",
- type=str,
- metavar="FILE",
- nargs="*",
- help="Path to model checkpoint (.pt file)",
- )
files_args.add_argument(
"--data_path",
type=str,
metavar="FILE",
help="Path to CSV file containing testing data for which predictions will be made",
- default="",
)
- files_args.add_argument(
- "--test_path",
- type=str,
- metavar="FILE",
- help="Path to CSV file containing testing data for which predictions will be made",
- default="",
- )
- files_args.add_argument(
- "--features_path",
- type=str,
- metavar="FILE",
- nargs="*",
- help="Path to features to use in FNN (instead of features_generator)",
- )
- files_args.add_argument(
- "--atom_descriptors_path",
- type=str,
- metavar="FILE",
- help="Path to the extra atom descriptors.",
- )
- data_args.add_argument(
- "--use_compound_names",
- action="store_true",
- default=False,
- help="Use when test data file contains compound names in addition to SMILES strings",
- )
- data_args.add_argument(
- "--no_features_scaling",
- action="store_true",
- default=False,
- help="Turn off scaling of features",
- )
- data_args.add_argument(
- "--max_data_size",
+ interpret_args.add_argument(
+ "--max_atoms",
type=int,
metavar="INT",
- help="Maximum number of data points to load",
+ help="Maximum number of atoms to use for interpretation",
)
- data_args.add_argument(
- "--smiles_columns",
- type=str,
- metavar="STRING",
- help="List of names of the columns containing SMILES strings.By default, uses the first\
- number_of_molecules columns.",
- )
- data_args.add_argument(
- "--number_of_molecules",
+
+ interpret_args.add_argument(
+ "--min_atoms",
type=int,
metavar="INT",
- help="Number of molecules in each input to the model.This must equal the length of\
- smiles_columns if not None",
+ help="Minimum number of atoms to use for interpretation",
)
- data_args.add_argument(
- "--atom_descriptors",
- type=bool,
- metavar="Bool",
- help="Use or not atom descriptors",
+ interpret_args.add_argument(
+ "--prop_delta",
+ type=float,
+ metavar="FLOAT",
+ help="The minimum change in the property of interest that is considered significant",
)
-
- data_args.add_argument(
- "--bond_features_size",
+ interpret_args.add_argument(
+ "--property_id",
type=int,
metavar="INT",
- help="Size of the extra bond descriptors that will be used as bond features to featurize a\
- given molecule",
+ help="The index of the property of interest",
)
- training_args.add_argument(
- "--batch_size", type=int, metavar="INT", default=50, help="Batch size"
+ # write the argument for rollouts
+ interpret_args.add_argument(
+ "--rollout",
+ type=int,
+ metavar="INT",
+ help="The number of rollouts to use for interpretation",
)
diff --git a/dfpl/plot.py b/dfpl/plot.py
old mode 100644
new mode 100755
diff --git a/dfpl/predictions.py b/dfpl/predictions.py
old mode 100644
new mode 100755
diff --git a/dfpl/settings.py b/dfpl/settings.py
old mode 100644
new mode 100755
diff --git a/dfpl/single_label_model.py b/dfpl/single_label_model.py
old mode 100644
new mode 100755
index 18402f09..d1ee3feb
--- a/dfpl/single_label_model.py
+++ b/dfpl/single_label_model.py
@@ -21,13 +21,13 @@
)
from sklearn.model_selection import StratifiedKFold, train_test_split
from tensorflow.keras import metrics, optimizers, regularizers
-from tensorflow.keras.layers import AlphaDropout, Dense, Dropout
+from tensorflow.keras.layers import AlphaDropout, Dense, Dropout, InputLayer
from tensorflow.keras.losses import (
BinaryCrossentropy,
BinaryFocalCrossentropy,
MeanSquaredError,
)
-from tensorflow.keras.models import Model, Sequential
+from tensorflow.keras.models import Model, Sequential, load_model
from dfpl import callbacks as cb
from dfpl import options
@@ -180,79 +180,119 @@ def prepare_nn_training_data(
# This function defines a feedforward neural network (FNN) with the given input size, options, and output bias
def build_fnn_network(
- input_size: int, opts: options.Options, output_bias=None
+ input_size: int, opts: options.Options, output_bias=None, encoder: Model = None
) -> Model:
+ # Assuming the last layer of the encoder is the input to the FNN
+ input_size = encoder.layers[-1].output_shape[-1]
+ # Rename encoder layers for clarity
+ for i, layer in enumerate(encoder.layers):
+ layer._name = f'encoder_layer_{i + 1}'
+
+ # Start model with encoder output
+ x = encoder.output
+
# Set the output bias if it is provided
if output_bias is not None:
output_bias = tf.keras.initializers.Constant(output_bias)
- # Define the number of hidden layers based on the input size
- my_hidden_layers = {"2048": 6, "1024": 5, "999": 5, "512": 4, "256": 3}
- if not str(input_size) in my_hidden_layers.keys():
- raise ValueError("Wrong input-size. Must be in {2048, 1024, 999, 512, 256}.")
+ # Define the number of hidden layers dynamically based on the input size
nhl = int(math.log2(input_size) / 2 - 1)
+ my_hidden_layers = {"2048": 6, "1024": 5, "999": 5, "512": 4, "256": 3}
+ if str(input_size) not in my_hidden_layers.keys():
+ raise ValueError("Input size not supported. Must be in {2048, 1024, 999, 512, 256}.")
- # Create a sequential model
- model = Sequential()
-
- # Add the first hidden layer
- if opts.activationFunction == "relu":
- model.add(
- Dense(
- units=int(input_size / 2),
- input_dim=input_size,
- activation="relu",
- kernel_regularizer=regularizers.l2(opts.l2reg),
- kernel_initializer="he_uniform",
- )
- )
- model.add(Dropout(opts.dropout))
- elif opts.activationFunction == "selu":
- model.add(
- Dense(
- units=int(input_size / 2),
- input_dim=input_size,
- activation="selu",
- kernel_initializer="lecun_normal",
- )
- )
- model.add(AlphaDropout(opts.dropout))
- else:
- logging.error("Only 'relu' and 'selu' activation is supported")
- sys.exit(-1)
+ # Add hidden layers
+ for i in range(nhl):
+ units = int(input_size / 2 ** (i + 1))
+ dropout_rate = opts.dropout / (2 * i + 1) if i > 0 else opts.dropout
- # Add additional hidden layers
- for i in range(1, nhl):
- factor_units = 2 ** (i + 1)
- factor_dropout = 2 * i
if opts.activationFunction == "relu":
- model.add(
- Dense(
- units=int(input_size / factor_units),
- activation="relu",
- kernel_regularizer=regularizers.l2(opts.l2reg),
- kernel_initializer="he_uniform",
- )
- )
- model.add(Dropout(opts.dropout / factor_dropout))
+ x = Dense(units, activation="relu", kernel_regularizer=regularizers.l2(opts.l2reg),
+ kernel_initializer="he_uniform")(x)
+ x = Dropout(dropout_rate)(x)
elif opts.activationFunction == "selu":
- model.add(
- Dense(
- units=int(input_size / factor_units),
- activation="selu",
- kernel_initializer="lecun_normal",
- )
- )
- model.add(AlphaDropout(opts.dropout / factor_dropout))
+ x = Dense(units, activation="selu", kernel_initializer="lecun_normal")(x)
+ x = AlphaDropout(dropout_rate)(x)
else:
- logging.error("Only 'relu' and 'selu' activation is supported")
+ logging.error("Unsupported activation function. Only 'relu' and 'selu' are supported.")
sys.exit(-1)
- # Add the output layer with a sigmoid activation function and the output bias if provided
- model.add(Dense(units=1, activation="sigmoid", bias_initializer=output_bias))
+ # Add the output layer
+ outputs = Dense(units=1, activation="sigmoid", bias_initializer=output_bias)(x)
+
+ # Create the model
+ model = Model(inputs=encoder.input, outputs=outputs)
model.summary()
+
return model
+ # # Define the number of hidden layers based on the input size
+ # my_hidden_layers = {"2048": 6, "1024": 5, "999": 5, "512": 4, "256": 3}
+ # if not str(input_size) in my_hidden_layers.keys():
+ # raise ValueError("Wrong input-size. Must be in {2048, 1024, 999, 512, 256}.")
+ # nhl = int(math.log2(input_size) / 2 - 1)
+ #
+ # # Create a sequential model
+ # model = Sequential()
+ #
+ # # Add the first hidden layer
+ # if opts.activationFunction == "relu":
+ # model.add(
+ # Dense(
+ # units=int(input_size / 2),
+ # input_dim=input_size,
+ # activation="relu",
+ # kernel_regularizer=regularizers.l2(opts.l2reg),
+ # kernel_initializer="he_uniform",
+ # )
+ # )
+ # model.add(Dropout(opts.dropout))
+ # elif opts.activationFunction == "selu":
+ # model.add(
+ # Dense(
+ # units=int(input_size / 2),
+ # input_dim=input_size,
+ # activation="selu",
+ # kernel_initializer="lecun_normal",
+ # )
+ # )
+ # model.add(AlphaDropout(opts.dropout))
+ # else:
+ # logging.error("Only 'relu' and 'selu' activation is supported")
+ # sys.exit(-1)
+ #
+ # # Add additional hidden layers
+ # for i in range(1, nhl):
+ # factor_units = 2 ** (i + 1)
+ # factor_dropout = 2 * i
+ # if opts.activationFunction == "relu":
+ # model.add(
+ # Dense(
+ # units=int(input_size / factor_units),
+ # activation="relu",
+ # kernel_regularizer=regularizers.l2(opts.l2reg),
+ # kernel_initializer="he_uniform",
+ # )
+ # )
+ # model.add(Dropout(opts.dropout / factor_dropout))
+ # elif opts.activationFunction == "selu":
+ # model.add(
+ # Dense(
+ # units=int(input_size / factor_units),
+ # activation="selu",
+ # kernel_initializer="lecun_normal",
+ # )
+ # )
+ # model.add(AlphaDropout(opts.dropout / factor_dropout))
+ # else:
+ # logging.error("Only 'relu' and 'selu' activation is supported")
+ # sys.exit(-1)
+ #
+ # # Add the output layer with a sigmoid activation function and the output bias if provided
+ # model.add(Dense(units=1, activation="sigmoid", bias_initializer=output_bias))
+ # model.summary()
+ # return model
+
# This function defines a shallow neural network (SNN) with the given input size, options, and output bias
def build_snn_network(
@@ -333,19 +373,27 @@ def define_single_label_model(
else:
logging.error(f"Your selected loss is not supported: {opts.lossFunction}.")
sys.exit("Unsupported loss function")
-
+ lr_schedule = optimizers.schedules.ExponentialDecay(
+ opts.learningRate,
+ decay_steps=1000,
+ decay_rate=opts.learningRateDecay,
+ staircase=True,
+ )
# Set the optimizer according to the option selected
if opts.optimizer == "Adam":
- my_optimizer = optimizers.Adam(learning_rate=opts.learningRate)
+ my_optimizer = optimizers.legacy.Adam(learning_rate=lr_schedule)
elif opts.optimizer == "SGD":
- my_optimizer = optimizers.SGD(lr=opts.learningRate, momentum=0.9)
+ my_optimizer = optimizers.legacy.SGD(lr=lr_schedule, momentum=0.9)
else:
logging.error(f"Your selected optimizer is not supported: {opts.optimizer}.")
sys.exit("Unsupported optimizer")
-
+ if opts.finetuneEncoder:
+ encoder = load_model(opts.ecModelDir) # Ensure this loads the correct model
+ else:
+ encoder = None
# Set the type of neural network according to the option selected
if opts.fnnType == "FNN":
- model = build_fnn_network(input_size, opts, output_bias)
+ model = build_fnn_network(input_size, opts, output_bias,encoder=encoder)
elif opts.fnnType == "SNN":
model = build_snn_network(input_size, opts, output_bias)
else:
@@ -489,8 +537,10 @@ def fit_and_evaluate_model(
y_test: np.ndarray,
fold: int,
target: str,
- opts: options.Options,
+ opts: options.Options
) -> pd.DataFrame:
+
+
# Print info about training
logging.info(f"Training of fold number: {fold}")
@@ -544,6 +594,15 @@ def fit_and_evaluate_model(
# Evaluate model
callback_model = define_single_label_model(input_size=x_train.shape[1], opts=opts)
callback_model.load_weights(filepath=checkpoint_model_weights_path)
+ callback_model.save_weights(
+ path.join(
+ opts.outputDir,
+ f"{target}_single-labeled_Fold-{fold}.model.weights.hdf5",
+ )
+ )
+ callback_model.save(
+ filepath=path.join(opts.outputDir, f"{target}-{fold}_saved_model")
+ )
performance = evaluate_model(
x_test=x_test,
y_test=y_test,
@@ -553,6 +612,7 @@ def fit_and_evaluate_model(
fold=fold,
)
+
return performance
@@ -595,12 +655,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
:param df: The dataframe containing x matrix and at least one column for a y target.
"""
- # find target columns
- targets = [
- c
- for c in df.columns
- if c in ["AR", "ER", "ED", "TR", "GR", "PPARg", "Aromatase"]
- ]
+ targets = [c for c in df.columns if c not in ["smiles", "fp", "fpcompressed"]]
if opts.wabTracking and opts.wabTarget != "":
# For W&B tracking, we only train one target that's specified as wabTarget "ER".
# In case it's not there, we use the first one available
@@ -617,7 +672,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
# Collect metrics for each fold and target
performance_list = []
if opts.split_type == "random":
- for target in targets: # [:1]:
+ for target in targets:
# target=targets[1] # --> only for testing the code
x, y = prepare_nn_training_data(df, target, opts, return_dataframe=False)
if x is None:
@@ -630,7 +685,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
# for single 'folds' and when sweeping on W&B, we fix the random state
if opts.wabTracking and not opts.aeWabTracking:
wandb.init(
- project=f"FFN_{opts.split_type}",
+ project=f"May_FFN_{opts.split_type}",
group=f"{target}",
name=f"{target}_single_fold",
)
@@ -642,7 +697,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
reinit=True,
)
x_train, x_test, y_train, y_test = train_test_split(
- x, y, stratify=y, test_size=opts.testSize, random_state=1
+ x, y, stratify=y, test_size=opts.testSize, random_state=0
)
logging.info(
f"Splitting train/test data with fixed random initializer"
@@ -663,32 +718,19 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
)
performance_list.append(performance)
# save complete model
- trained_model = define_single_label_model(
- input_size=len(x[0]), opts=opts
- )
- # trained_model.load_weights
- # (path.join(opts.outputDir, f"{target}_single-labeled_Fold-0.model.weights.hdf5"))
- trained_model.save_weights(
- path.join(
- opts.outputDir,
- f"{target}_single-labeled_Fold-0.model.weights.hdf5",
- )
- )
- trained_model.save(
- filepath=path.join(opts.outputDir, f"{target}_saved_model")
- )
+
elif 1 < opts.kFolds < 10: # int(x.shape[0] / 100):
# do a kfold cross-validation
kfold_c_validator = StratifiedKFold(
- n_splits=opts.kFolds, shuffle=True, random_state=42
+ n_splits=opts.kFolds, shuffle=True, random_state=0
)
fold_no = 1
# split the data
for train, test in kfold_c_validator.split(x, y):
if opts.wabTracking and not opts.aeWabTracking:
wandb.init(
- project=f"FNN_{opts.threshold}_{opts.split_type}",
+ project=f"May_FNN_{opts.threshold}_{opts.split_type}",
group=f"{target}",
name=f"{target}-{fold_no}",
reinit=True,
@@ -720,28 +762,15 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
)
performance_list.append(performance)
- # save complete model
- trained_model = define_single_label_model(
- input_size=len(x[0]), opts=opts
- )
- # trained_model.load_weights
- # (path.join(opts.outputDir, f"{target}_single-labeled_Fold-0.model.weights.hdf5"))
- trained_model.save_weights(
- path.join(
- opts.outputDir,
- f"{target}_single-labeled_Fold-{fold_no}.model.weights.hdf5",
- )
- )
- # create output directory and store complete model
- trained_model.save(
- filepath=path.join(
- opts.outputDir, f"{target}-{fold_no}_saved_model"
- )
- )
if opts.wabTracking:
wandb.finish()
- fold_no += 1
+ train_indices_path = os.path.join(opts.outputDir, f"fold_{fold_no}_train_indices.csv")
+ test_indices_path = os.path.join(opts.outputDir, f"fold_{fold_no}_test_indices.csv")
+ # Save the indices to CSV files
+ pd.DataFrame(train, columns=['Index']).to_csv(train_indices_path, index=False)
+ pd.DataFrame(test, columns=['Index']).to_csv(test_indices_path, index=False)
+ fold_no += 1
# select and copy best model - how to define the best model?
best_fold = pd.concat(performance_list, ignore_index=True).sort_values(
by=["p_1", "r_1", "MCC"], ascending=False, ignore_index=True
@@ -749,11 +778,11 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
# rename the fold to best fold
src = os.path.join(
opts.outputDir,
- f"{target}_single-labeled_Fold-{best_fold}.model.weights.hdf5",
+ f"{target}_{opts.split_type}_single-labeled_Fold-{best_fold}.model.weights.hdf5",
)
dst = os.path.join(
opts.outputDir,
- f"{target}_single-labeled_Best_Fold-{best_fold}.model.weights.hdf5",
+ f"{target}_{opts.split_type}_single-labeled_Best_Fold-{best_fold}.model.weights.hdf5",
)
os.rename(src, dst)
@@ -770,7 +799,6 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
# Rename source directory to destination directory
os.rename(src_dir, dst_dir)
- # save complete model
else:
logging.info(
"Your selected number of folds for Cross validation is out of range. "
@@ -780,13 +808,12 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
(
pd.concat(performance_list, ignore_index=True).to_csv(
path_or_buf=path.join(
- opts.outputDir, "single_label_random_model.evaluation.csv"
+ opts.outputDir, f"single_label_{opts.split_type}_model.evaluation.csv"
)
)
)
# For each individual target train a model
elif opts.split_type == "scaffold_balanced":
- # df, irrelevant_columns = preprocess_dataframe(df, opts)
for idx, target in enumerate(targets):
df = prepare_nn_training_data(df, target, opts, return_dataframe=True)
relevant_cols = ["smiles"] + ["fp"] + [target] # list(irrelevant_columns)
@@ -808,7 +835,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
)
if opts.wabTracking and not opts.aeWabTracking:
wandb.init(
- project=f"FFN_{opts.split_type}",
+ project=f"May_FFN_{opts.split_type}",
group=f"{target}",
name=f"{target}_single_fold",
)
@@ -829,20 +856,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
opts=opts,
)
performance_list.append(performance)
- trained_model = define_single_label_model(
- input_size=len(x_train[0]), opts=opts
- )
- trained_model.save_weights(
- path.join(
- opts.outputDir,
- f"{target}_scaffold_single-labeled_Fold-0.model.weights.hdf5",
- )
- )
- trained_model.save(
- filepath=path.join(
- opts.outputDir, f"{target}_scaffold_saved_model_0"
- )
- )
+
elif opts.kFolds > 1:
for fold_no in range(1, opts.kFolds + 1):
print(f"Splitting data with seed {fold_no}")
@@ -857,7 +871,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
)
if opts.wabTracking and not opts.aeWabTracking:
wandb.init(
- project=f"FFN_{opts.split_type}",
+ project=f"May_FFN_{opts.split_type}",
group=f"{target}",
name=f"{target}-{fold_no}",
reinit=True,
@@ -880,20 +894,6 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
)
performance_list.append(performance)
- trained_model = define_single_label_model(
- input_size=len(x_train[0]), opts=opts
- )
- trained_model.save_weights(
- path.join(
- opts.outputDir,
- f"{target}_scaffold_single-labeled_Fold-{fold_no}.model.weights.hdf5",
- )
- )
- trained_model.save(
- filepath=path.join(
- opts.outputDir, f"{target}_scaffold_saved_model_{fold_no}"
- )
- )
if opts.wabTracking:
wandb.finish()
fold_no += 1
@@ -904,20 +904,20 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
# rename the fold to best fold
src = os.path.join(
opts.outputDir,
- f"{target}_scaffold_single-labeled_Fold-{best_fold}.model.weights.hdf5",
+ f"{target}_{opts.split_type}_single-labeled_Fold-{best_fold}.model.weights.hdf5",
)
dst = os.path.join(
opts.outputDir,
- f"{target}_scaffold_single-labeled_BEST_Fold-{best_fold}.model.weights.hdf5",
+ f"{target}_{opts.split_type}_single-labeled_BEST_Fold-{best_fold}.model.weights.hdf5",
)
os.rename(src, dst)
src_dir = os.path.join(
- opts.outputDir, f"{target}_scaffold_saved_model_{best_fold}"
+ opts.outputDir, f"{target}_{opts.split_type}_saved_model_{best_fold}"
)
dst_dir = os.path.join(
opts.outputDir,
- f"{target}_scaffold_saved_model_BEST_FOLD_{best_fold}",
+ f"{target}_{opts.split_type}_saved_model_BEST_FOLD_{best_fold}",
)
if path.isdir(dst_dir):
@@ -935,7 +935,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
(
pd.concat(performance_list, ignore_index=True).to_csv(
path_or_buf=path.join(
- opts.outputDir, "single_label_scaffold_model.evaluation.csv"
+ opts.outputDir, f"single_label_{opts.split_type}_model.evaluation.csv"
)
)
)
@@ -958,7 +958,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
)
if opts.wabTracking and not opts.aeWabTracking:
wandb.init(
- project=f"FFN_{opts.split_type}AE_{opts.aeSplitType}",
+ project=f"May_FFN_{opts.split_type}AE_{opts.aeSplitType}",
group=f"{target}",
name=f"{target}_single_fold",
)
@@ -978,18 +978,7 @@ def train_single_label_models(df: pd.DataFrame, opts: options.Options) -> None:
opts=opts,
)
performance_list.append(performance)
- trained_model = define_single_label_model(
- input_size=len(x_train[0]), opts=opts
- )
- trained_model.save_weights(
- path.join(
- opts.outputDir,
- f"{target}_weight_single-labeled_Fold-0.model.weights.hdf5",
- )
- )
- trained_model.save(
- filepath=path.join(opts.outputDir, f"{target}_weight_saved_model_0")
- )
+
elif opts.kFolds > 1:
raise Exception(
f"Unsupported number of folds: {opts.kFolds} for {opts.split_type} split.\
diff --git a/dfpl/utils.py b/dfpl/utils.py
old mode 100644
new mode 100755
index db3d6ec1..78ad47e4
--- a/dfpl/utils.py
+++ b/dfpl/utils.py
@@ -1,12 +1,16 @@
+import argparse
import json
import logging
import os
import pathlib
+import sys
import warnings
from collections import defaultdict
+from pathlib import Path
from random import Random
-from typing import Dict, List, Set, Tuple, Union
+from typing import Dict, List, Set, Tuple, Type, TypeVar, Union
+import jsonpickle
import numpy as np
import pandas as pd
from rdkit import Chem, RDLogger
@@ -14,9 +18,49 @@
from rdkit.Chem.Scaffolds import MurckoScaffold
from tqdm import tqdm
+# Define a type variable
+
+
RDLogger.DisableLog("rdApp.*")
+T = TypeVar("T")
+def parseCmdArgs(cls: Type[T], args: argparse.Namespace) -> T:
+ """
+ Parses command-line arguments to create an instance of the given class.
+
+ Args:
+ cls: The class to create an instance of.
+ args: argparse.Namespace containing the command-line arguments.
+
+ Returns:
+ An instance of cls populated with values from the command-line arguments.
+ """
+ # Extract argument flags from sys.argv
+ arg_flags = {arg.lstrip("-") for arg in sys.argv if arg.startswith("-")}
+
+ # Create the result instance, which will be modified and returned
+ result = cls()
+
+ # Load JSON file if specified
+ if hasattr(args, "configFile") and args.configFile:
+ jsonFile = Path(args.configFile)
+ if jsonFile.exists() and jsonFile.is_file():
+ with jsonFile.open() as f:
+ content = jsonpickle.decode(f.read())
+ for key, value in vars(content).items():
+ setattr(result, key, value)
+ else:
+ raise ValueError("Could not find JSON input file")
+
+ # Override with user-provided command-line arguments
+ for key in arg_flags:
+ if hasattr(args, key):
+ user_value = getattr(args, key, None)
+ setattr(result, key, user_value)
+
+ return result
+
def makePathAbsolute(p: str) -> str:
path = pathlib.Path(p)
if path.is_absolute():
@@ -30,24 +74,100 @@ def createDirectory(directory: str):
if not os.path.exists(path):
os.makedirs(path)
-
-def createArgsFromJson(in_json: str, ignore_elements: list, return_json_object: bool):
+def parse_cli_list(value: str):
+ # Simple parser for lists passed as comma-separated values
+ return value.split(',')
+
+def parse_cli_boolean(cli_args, cli_arg_key):
+ # Determines boolean value based on command line presence
+ if cli_arg_key in cli_args:
+ return True # Presence of flag implies True
+ return False
+
+# def createArgsFromJson(jsonFile: str):
+# arguments = []
+# ignore_elements = ["py/object"]
+# cli_args = sys.argv[1:] # Skipping the script name itself
+#
+# with open(jsonFile, "r") as f:
+# data = json.load(f)
+#
+# processed_cli_keys = [] # To track which CLI keys have been processed
+#
+# for key, value in data.items():
+# if key not in ignore_elements:
+# cli_arg_key = f"--{key}"
+# if cli_arg_key in cli_args:
+# processed_cli_keys.append(cli_arg_key)
+# arg_index = cli_args.index(cli_arg_key) + 1
+# if isinstance(value, bool):
+# value = parse_cli_boolean(cli_args, cli_arg_key)
+# elif arg_index < len(cli_args):
+# cli_value = cli_args[arg_index]
+# if isinstance(value, list):
+# value = parse_cli_list(cli_value)
+# else:
+# value = cli_value # Override JSON value with command-line value
+# if isinstance(value, bool) and value:
+# arguments.append(cli_arg_key)
+# elif isinstance(value, list):
+# arguments.append(cli_arg_key)
+# arguments.extend(map(str, value)) # Ensure all elements are strings
+# else:
+# arguments.extend([cli_arg_key, str(value)])
+#
+# return arguments
+def createArgsFromJson(jsonFile: str) -> List[str]:
arguments = []
- with open(in_json, "r") as f:
+ ignore_elements = ["py/object"]
+ cli_args = sys.argv[1:] # Skipping the script name itself
+
+ with open(jsonFile, "r") as f:
data = json.load(f)
+
+ processed_cli_keys = [] # To track which CLI keys have been processed
+
for key, value in data.items():
if key not in ignore_elements:
- if key == "extra_metrics" and isinstance(value, list):
- arguments.append("--extra_metrics")
- arguments.extend(value)
+ cli_arg_key = f"--{key}"
+ if cli_arg_key in cli_args:
+ processed_cli_keys.append(cli_arg_key)
+ arg_index = cli_args.index(cli_arg_key) + 1
+ if isinstance(value, bool):
+ value = parse_cli_boolean(cli_args, cli_arg_key)
+ elif arg_index < len(cli_args) and not cli_args[arg_index].startswith('--'):
+ cli_value = cli_args[arg_index]
+ if isinstance(value, list):
+ value = parse_cli_list(cli_value)
+ else:
+ value = cli_value # Override JSON value with command-line value
+ if isinstance(value, bool):
+ if value:
+ arguments.append(cli_arg_key)
+ elif isinstance(value, list):
+ arguments.append(cli_arg_key)
+ arguments.extend(map(str, value)) # Ensure all elements are strings
else:
- arguments.append("--" + str(key))
- arguments.append(str(value))
- if return_json_object:
- return arguments, data
- return arguments
-
+ arguments.extend([cli_arg_key, str(value)])
+ i = 0
+ while i < len(cli_args):
+ arg = cli_args[i]
+ if arg.startswith("--"):
+ key = arg.lstrip("--")
+ if key not in data:
+ value = True if i + 1 >= len(cli_args) or cli_args[i + 1].startswith("--") else cli_args[i + 1]
+ if isinstance(value, bool):
+ if value:
+ arguments.append(arg)
+ else:
+ arguments.extend([arg, str(value)])
+ i += 1 if isinstance(value, bool) else 2
+ else:
+ i += 1
+ else:
+ i += 1
+ return arguments
def make_mol(s: str, keep_h: bool, add_h: bool, keep_atom_map: bool):
"""
Builds an RDKit molecule from a SMILES string.
@@ -76,49 +196,6 @@ def make_mol(s: str, keep_h: bool, add_h: bool, keep_atom_map: bool):
return mol
-def generate_scaffold(
- mol: Union[str, Chem.Mol, Tuple[Chem.Mol, Chem.Mol]], include_chirality: bool = True
-) -> str:
- """
- Computes the Bemis-Murcko scaffold for a SMILES string, an RDKit molecule, or an InChI string or InChIKey.
-
- :param mol: A SMILES, RDKit molecule, InChI string, or InChIKey string.
- :param include_chirality: Whether to include chirality in the computed scaffold.
- :return: The Bemis-Murcko scaffold for the molecule.
- """
- if isinstance(mol, str):
- if mol.startswith("InChI="):
- mol = inchi_to_mol(mol)
- else:
- mol = make_mol(mol, keep_h=False, add_h=False, keep_atom_map=False)
- elif isinstance(mol, tuple):
- mol = mol[0]
- scaffold = MurckoScaffold.MurckoScaffoldSmiles(
- mol=mol, includeChirality=include_chirality
- )
-
- return scaffold
-
-
-def scaffold_to_smiles(
- mols: List[str], use_indices: bool = False
-) -> Dict[str, Union[Set[str], Set[int]]]:
- """
- Computes the scaffold for each SMILES and returns a mapping from scaffolds to sets of smiles (or indices).
- :param mols: A list of SMILES.
- :param use_indices: Whether to map to the SMILES's index in :code:`mols` rather than
- mapping to the smiles string itself. This is necessary if there are duplicate smiles.
- :return: A dictionary mapping each unique scaffold to all SMILES (or indices) which have that scaffold.
- """
- scaffolds = defaultdict(set)
- for i, mol in tqdm(enumerate(mols), total=len(mols)):
- scaffold = generate_scaffold(mol)
- if use_indices:
- scaffolds[scaffold].add(i)
- else:
- scaffolds[scaffold].add(mol)
-
- return scaffolds
# def inchi_to_mol(inchi: str) -> Chem.Mol:
@@ -184,7 +261,49 @@ def weight_split(
test_df = sorted_data.iloc[test_indices].reset_index(drop=True)
return train_df, val_df, test_df
+def generate_scaffold(
+ mol: Union[str, Chem.Mol, Tuple[Chem.Mol, Chem.Mol]], include_chirality: bool = True
+) -> str:
+ """
+ Computes the Bemis-Murcko scaffold for a SMILES string, an RDKit molecule, or an InChI string or InChIKey.
+
+ :param mol: A SMILES, RDKit molecule, InChI string, or InChIKey string.
+ :param include_chirality: Whether to include chirality in the computed scaffold.
+ :return: The Bemis-Murcko scaffold for the molecule.
+ """
+ if isinstance(mol, str):
+ if mol.startswith("InChI="):
+ mol = inchi_to_mol(mol)
+ else:
+ mol = make_mol(mol, keep_h=False, add_h=False, keep_atom_map=False)
+ elif isinstance(mol, tuple):
+ mol = mol[0]
+ scaffold = MurckoScaffold.MurckoScaffoldSmiles(
+ mol=mol, includeChirality=include_chirality
+ )
+
+ return scaffold
+
+
+def scaffold_to_smiles(
+ mols: List[str], use_indices: bool = False
+) -> Dict[str, Union[Set[str], Set[int]]]:
+ """
+ Computes the scaffold for each SMILES and returns a mapping from scaffolds to sets of smiles (or indices).
+ :param mols: A list of SMILES.
+ :param use_indices: Whether to map to the SMILES's index in :code:`mols` rather than
+ mapping to the smiles string itself. This is necessary if there are duplicate smiles.
+ :return: A dictionary mapping each unique scaffold to all SMILES (or indices) which have that scaffold.
+ """
+ scaffolds = defaultdict(set)
+ for i, mol in tqdm(enumerate(mols), total=len(mols)):
+ scaffold = generate_scaffold(mol)
+ if use_indices:
+ scaffolds[scaffold].add(i)
+ else:
+ scaffolds[scaffold].add(mol)
+ return scaffolds
def ae_scaffold_split(
data: pd.DataFrame,
diff --git a/dfpl/vae.py b/dfpl/vae.py
old mode 100644
new mode 100755
index d0a89dbe..4c568dc7
--- a/dfpl/vae.py
+++ b/dfpl/vae.py
@@ -1,8 +1,6 @@
-import csv
import logging
import math
import os.path
-from os.path import basename
from typing import Tuple
import numpy as np
@@ -21,27 +19,31 @@
from dfpl import options, settings
from dfpl.utils import ae_scaffold_split, weight_split
-disable_eager_execution()
-
def define_vae_model(opts: options.Options, output_bias=None) -> Tuple[Model, Model]:
+ disable_eager_execution()
+
input_size = opts.fpSize
- encoding_dim = opts.encFPSize
- ac_optimizer = optimizers.Adam(
- learning_rate=opts.aeLearningRate, decay=opts.aeLearningRateDecay
+ encoding_dim = (
+ opts.encFPSize
+ ) # This should be the intended size of your latent space, e.g., 256
+
+ lr_schedule = optimizers.schedules.ExponentialDecay(
+ opts.aeLearningRate,
+ decay_steps=1000,
+ decay_rate=opts.aeLearningRateDecay,
+ staircase=True,
)
+ ac_optimizer = optimizers.legacy.Adam(learning_rate=lr_schedule)
if output_bias is not None:
output_bias = initializers.Constant(output_bias)
- # get the number of meaningful hidden layers (latent space included)
hidden_layer_count = round(math.log2(input_size / encoding_dim))
- # the input placeholder
input_vec = Input(shape=(input_size,))
- # 1st hidden layer, that receives weights from input layer
- # equals bottleneck layer, if hidden_layer_count==1!
+ # 1st hidden layer
if opts.aeActivationFunction != "selu":
encoded = Dense(
units=int(input_size / 2), activation=opts.aeActivationFunction
@@ -53,87 +55,81 @@ def define_vae_model(opts: options.Options, output_bias=None) -> Tuple[Model, Mo
kernel_initializer="lecun_normal",
)(input_vec)
- if hidden_layer_count > 1:
- # encoding layers, incl. bottle-neck
- for i in range(1, hidden_layer_count):
- factor_units = 2 ** (i + 1)
- # print(f'{factor_units}: {int(input_size / factor_units)}')
- if opts.aeActivationFunction != "selu":
- encoded = Dense(
- units=int(input_size / factor_units),
- activation=opts.aeActivationFunction,
- )(encoded)
- else:
- encoded = Dense(
- units=int(input_size / factor_units),
- activation=opts.aeActivationFunction,
- kernel_initializer="lecun_normal",
- )(encoded)
-
- # latent space layers
- factor_units = 2 ** (hidden_layer_count - 1)
+ # encoding layers
+ for i in range(
+ 1, hidden_layer_count - 1
+ ): # Adjust the range to stop before the latent space layers
+ factor_units = 2 ** (i + 1)
if opts.aeActivationFunction != "selu":
- z_mean = Dense(
- units=int(input_size / factor_units),
- activation=opts.aeActivationFunction,
- )(encoded)
- z_log_var = Dense(
+ encoded = Dense(
units=int(input_size / factor_units),
activation=opts.aeActivationFunction,
)(encoded)
else:
- z_mean = Dense(
+ encoded = Dense(
units=int(input_size / factor_units),
activation=opts.aeActivationFunction,
kernel_initializer="lecun_normal",
)(encoded)
- z_log_var = Dense(
+
+ # latent space layers
+ if opts.aeActivationFunction != "selu":
+ z_mean = Dense(units=encoding_dim, activation=opts.aeActivationFunction)(
+ encoded
+ ) # Adjusted size to encoding_dim
+ z_log_var = Dense(units=encoding_dim, activation=opts.aeActivationFunction)(
+ encoded
+ ) # Adjusted size to encoding_dim
+ else:
+ z_mean = Dense(
+ units=encoding_dim,
+ activation=opts.aeActivationFunction,
+ kernel_initializer="lecun_normal",
+ )(
+ encoded
+ ) # Adjusted size to encoding_dim
+ z_log_var = Dense(
+ units=encoding_dim,
+ activation=opts.aeActivationFunction,
+ kernel_initializer="lecun_normal",
+ )(
+ encoded
+ ) # Adjusted size to encoding_dim
+
+ # sampling layer
+ def sampling(args):
+ z_mean, z_log_var = args
+ batch = K.shape(z_mean)[0]
+ dim = K.int_shape(z_mean)[1]
+ epsilon = K.random_normal(shape=(batch, dim))
+ return z_mean + K.exp(0.5 * z_log_var) * epsilon
+
+ z = Lambda(sampling, output_shape=(encoding_dim,))([z_mean, z_log_var])
+ decoded = z
+
+ # decoding layers
+ for i in range(hidden_layer_count - 2, 0, -1):
+ factor_units = 2**i
+ if opts.aeActivationFunction != "selu":
+ decoded = Dense(
+ units=int(input_size / factor_units),
+ activation=opts.aeActivationFunction,
+ )(decoded)
+ else:
+ decoded = Dense(
units=int(input_size / factor_units),
activation=opts.aeActivationFunction,
kernel_initializer="lecun_normal",
- )(encoded)
-
- # sampling layer
- def sampling(args):
- z_mean, z_log_var = args
- batch = K.shape(z_mean)[0]
- dim = K.int_shape(z_mean)[1]
- epsilon = K.random_normal(shape=(batch, dim))
- return z_mean + K.exp(0.5 * z_log_var) * epsilon
-
- # sample from latent space
- z = Lambda(sampling, output_shape=(int(input_size / factor_units),))(
- [z_mean, z_log_var]
- )
- decoded = z
- # decoding layers
- for i in range(hidden_layer_count - 2, 0, -1):
- factor_units = 2**i
- # print(f'{factor_units}: {int(input_size/factor_units)}')
- if opts.aeActivationFunction != "selu":
- decoded = Dense(
- units=int(input_size / factor_units),
- activation=opts.aeActivationFunction,
- )(decoded)
- else:
- decoded = Dense(
- units=int(input_size / factor_units),
- activation=opts.aeActivationFunction,
- kernel_initializer="lecun_normal",
- )(decoded)
-
- # output layer
- decoded = Dense(
- units=input_size, activation="sigmoid", bias_initializer=output_bias
- )(decoded)
+ )(decoded)
- else:
- # output layer
- decoded = Dense(
- units=input_size, activation="sigmoid", bias_initializer=output_bias
- )(encoded)
+ # output layer
+ decoded = Dense(
+ units=input_size, activation="sigmoid", bias_initializer=output_bias
+ )(decoded)
autoencoder = Model(input_vec, decoded)
+ encoder = Model(input_vec, z)
+ autoencoder.summary(print_fn=logging.info)
# KL divergence loss
def kl_loss(z_mean, z_log_var):
@@ -155,9 +151,6 @@ def vae_loss(y_true, y_pred):
optimizer=ac_optimizer, loss=vae_loss, metrics=[bce_loss, kl_loss]
)
- # build encoder model
- encoder = Model(input_vec, z_mean)
-
return autoencoder, encoder
@@ -175,39 +168,9 @@ def train_full_vae(df: pd.DataFrame, opts: options.Options) -> Model:
if opts.aeWabTracking and not opts.wabTracking:
wandb.init(project=f"VAE_{opts.aeSplitType}")
- # Define output files for VAE and encoder weights
- if opts.ecWeightsFile == "":
- # If no encoder weights file is specified, use the input file name to generate a default file name
- logging.info("No VAE encoder weights file specified")
- base_file_name = (
- os.path.splitext(basename(opts.inputFile))[0]
- + opts.aeType
- + opts.aeSplitType
- )
- logging.info(
- f"(variational) encoder weights will be saved in {base_file_name}.autoencoder.hdf5"
- )
- vae_weights_file = os.path.join(
- opts.outputDir, base_file_name + ".vae.weights.hdf5"
- )
- # ec_weights_file = os.path.join(
- # opts.outputDir, base_file_name + ".encoder.weights.hdf5"
- # )
- else:
- # If an encoder weights file is specified, use it as the encoder weights file name
- logging.info(f"VAE encoder will be saved in {opts.ecWeightsFile}")
- base_file_name = (
- os.path.splitext(basename(opts.ecWeightsFile))[0] + opts.aeSplitType
- )
- vae_weights_file = os.path.join(
- opts.outputDir, base_file_name + ".vae.weights.hdf5"
- )
- # ec_weights_file = os.path.join(opts.outputDir, opts.ecWeightsFile)
-
+ save_path = os.path.join(opts.ecModelDir, f"{opts.aeSplitType}_split_autoencoder")
# Collect the callbacks for training
- callback_list = callbacks.autoencoder_callback(
- checkpoint_path=vae_weights_file, opts=opts
- )
+
# Select all fingerprints that are valid and turn them into a numpy array
fp_matrix = np.array(
df[df["fp"].notnull()]["fp"].to_list(),
@@ -219,17 +182,17 @@ def train_full_vae(df: pd.DataFrame, opts: options.Options) -> Model:
)
assert 0.0 <= opts.testSize <= 0.5
if opts.aeSplitType == "random":
- logging.info("Training VAE using random split")
- train_indices = np.arange(fp_matrix.shape[0])
+ logging.info("Training autoencoder using random split")
+ initial_indices = np.arange(fp_matrix.shape[0])
if opts.testSize > 0.0:
# Split data into test and training data
if opts.aeWabTracking:
- x_train, x_test, _, _ = train_test_split(
- fp_matrix, train_indices, test_size=opts.testSize, random_state=42
+ x_train, x_test, train_indices, test_indices = train_test_split(
+ fp_matrix, initial_indices, test_size=opts.testSize, random_state=42
)
else:
- x_train, x_test, _, _ = train_test_split(
- fp_matrix, train_indices, test_size=opts.testSize, random_state=42
+ x_train, x_test, train_indices, test_indices = train_test_split(
+ fp_matrix, initial_indices, test_size=opts.testSize, random_state=42
)
else:
x_train = fp_matrix
@@ -255,6 +218,12 @@ def train_full_vae(df: pd.DataFrame, opts: options.Options) -> Model:
dtype=settings.ac_fp_numpy_type,
copy=settings.numpy_copy_values,
)
+ train_indices = df[
+ df.index.isin(train_data[train_data["fp"].notnull()].index)
+ ].index.to_numpy()
+ test_indices = df[
+ df.index.isin(test_data[test_data["fp"].notnull()].index)
+ ].index.to_numpy()
else:
x_train = fp_matrix
x_test = None
@@ -262,7 +231,6 @@ def train_full_vae(df: pd.DataFrame, opts: options.Options) -> Model:
logging.info("Training autoencoder using molecular weight split")
train_indices = np.arange(fp_matrix.shape[0])
if opts.testSize > 0.0:
- # if opts.aeWabTracking:
train_data, val_data, test_data = weight_split(
df, sizes=(1 - opts.testSize, 0.0, opts.testSize), bias="small"
)
@@ -276,16 +244,21 @@ def train_full_vae(df: pd.DataFrame, opts: options.Options) -> Model:
dtype=settings.ac_fp_numpy_type,
copy=settings.numpy_copy_values,
)
+ df_sorted = df.sort_values(by="mol_weight", ascending=True)
+ # Get the sorted indices from the sorted DataFrame
+ sorted_indices = df_sorted.index.to_numpy()
+
+ # Find the corresponding indices for train_data, val_data, and test_data in the sorted DataFrame
+ train_indices = sorted_indices[df.index.isin(train_data.index)]
+ # val_indices = sorted_indices[df.index.isin(val_data.index)]
+ test_indices = sorted_indices[df.index.isin(test_data.index)]
else:
x_train = fp_matrix
x_test = None
else:
raise ValueError(f"Invalid split type: {opts.split_type}")
- if opts.testSize > 0.0:
- train_indices = train_indices[train_indices < x_train.shape[0]]
- test_indices = np.arange(x_train.shape[0], x_train.shape[0] + x_test.shape[0])
- else:
- test_indices = None
+
+ # Calculate the initial bias aka the log ratio between 1's and 0'1 in all fingerprints
ids, counts = np.unique(x_train.flatten(), return_counts=True)
count_dict = dict(zip(ids, counts))
if count_dict[0] == 0:
@@ -304,34 +277,34 @@ def train_full_vae(df: pd.DataFrame, opts: options.Options) -> Model:
(vae, encoder) = define_vae_model(opts, output_bias=initial_bias)
# Train the VAE on the training data
+ callback_list = callbacks.autoencoder_callback(
+ checkpoint_path=f"{save_path}.h5", opts=opts
+ )
+
vae_hist = vae.fit(
x_train,
x_train,
epochs=opts.aeEpochs,
batch_size=opts.aeBatchSize,
verbose=opts.verbose,
- callbacks=callback_list,
+ callbacks=[callback_list],
validation_data=(x_test, x_test) if opts.testSize > 0.0 else None,
)
# Save the VAE weights
- logging.info(f"VAE weights stored in file: {vae_weights_file}")
ht.store_and_plot_history(
- base_file_name=os.path.join(opts.outputDir, base_file_name + ".VAE"),
+ base_file_name=save_path,
hist=vae_hist,
)
- save_path = os.path.join(opts.ecModelDir, f"{opts.aeSplitType}_VAE.h5")
- if opts.testSize > 0.0:
- (callback_vae, callback_encoder) = define_vae_model(opts)
- callback_vae.load_weights(filepath=vae_weights_file)
- callback_encoder.save(filepath=save_path)
- else:
- encoder.save(filepath=save_path)
- latent_space = encoder.predict(fp_matrix)
- latent_space_file = os.path.join(
- opts.outputDir, base_file_name + ".latent_space.csv"
- )
- with open(latent_space_file, "w", newline="") as file:
- writer = csv.writer(file)
- writer.writerows(latent_space)
+ # Re-define autoencoder and encoder using your function
+ callback_autoencoder, callback_encoder = define_vae_model(opts)
+ callback_autoencoder.load_weights(filepath=f"{save_path}.h5")
+
+ for i, layer in enumerate(callback_encoder.layers):
+ layer.set_weights(callback_autoencoder.layers[i].get_weights())
+
+ # Save the encoder model
+ encoder_save_path = f"{save_path}_encoder.h5"
+ callback_encoder.save_weights(filepath=encoder_save_path)
+
return encoder, train_indices, test_indices
diff --git a/environment.yml b/environment.yml
old mode 100644
new mode 100755
index 3c2e7a6c..f008b36b
--- a/environment.yml
+++ b/environment.yml
@@ -1,23 +1,27 @@
-name: dfplenv
+name: dfpl_env
channels:
- conda-forge
- defaults
dependencies:
# dev requirements
- - conda-build=3.21.8
- - conda=4.12.0
- - pip=22.0.4
- - pytest=7.1.1
+ - conda-build>=3.21.8
+ - conda>=4.12.0
+ - pip>=22.0.4
+ - pytest>=7.1.1
+ - python=3.8
# application requirements
- - jsonpickle=2.1
- - matplotlib=3.5.1
- - numpy=1.19.5
- - pandas=1.4.2
- - rdkit=2022.03.1
- - scikit-learn=1.0.2
- - seaborn=0.12.2
- - tensorflow-gpu=2.6.0
- - wandb=0.12
- - umap=0.1.1
+ - protobuf>=3.21.12
+ - jsonpickle>=2.1
+ - matplotlib>=3.5.1
+ - numpy=1.23.5
+ - rdkit>=2023.03.3
+ - scikit-learn>=1.0.2
+ - seaborn>=0.12.2
+ - tensorflow>=2.13
+ - wandb>=0.16.2
+ - umap-learn=0.5.5
+ - numba>=0.51.0
- pip:
- - git+https://github.com/soulios/chemprop.git@1d73523e49aa28a90b74edc04aaf45d7e124e338
\ No newline at end of file
+ - git+https://github.com/soulios/chemprop.git@cfcb67a
+
+
diff --git a/example/interpret.json b/example/interpret.json
new file mode 100644
index 00000000..cc0cea00
--- /dev/null
+++ b/example/interpret.json
@@ -0,0 +1,10 @@
+{
+ "data_path": "tests/data/S_dataset.csv",
+ "checkpoint_path": "dmpnn-tox21-cvnotest8020-30-multilabel-bce/fold_0/model_0/model.pt",
+ "preds_path": "interpretations.csv",
+ "max_atoms": 20,
+ "min_atoms": 8,
+ "prop_delta": 0.5,
+ "property_id": [1,2,3,4,5,6,7,8,9,10,11,12],
+ "rollout": 20
+}
\ No newline at end of file
diff --git a/example/predict.json b/example/predict.json
index 252965e3..c9b9e1f1 100755
--- a/example/predict.json
+++ b/example/predict.json
@@ -1,12 +1,12 @@
{
"py/object": "dfpl.options.Options",
- "inputFile": "tests/data/smiles.csv",
+ "inputFile": "tests/data/S_dataset.csv",
"outputDir": "example/results_predict/",
"outputFile": "smiles.csv",
- "ecModelDir": "example/results_train/random_autoencoder/",
+ "ecModelDir": "",
"ecWeightsFile": "",
- "fnnModelDir": "example/results_train/AR_saved_model",
- "compressFeatures": true,
- "trainAC": false,
+ "fnnModelDir": "example/results/AR_saved_model",
+ "aeType": "deterministic",
+ "compressFeatures": false,
"trainFNN": false
}
diff --git a/example/predictgnn.json b/example/predictgnn.json
old mode 100644
new mode 100755
index 157b5e05..e4f58e37
--- a/example/predictgnn.json
+++ b/example/predictgnn.json
@@ -1,7 +1,19 @@
{
"py/object": "dfpl.options.GnnOptions",
- "test_path": "tests/data/smiles.csv",
- "checkpoint_path": "dmpnn-random/fold_0/model_0/model.pt",
- "save_dir": "preds_dmpnn",
- "saving_name": "DMPNN_preds.csv"
-}
\ No newline at end of file
+ "checkpoint_path": "dmpnntox21-cv-8020/fold_1/model_0/model.pt",
+ "test_path": "tests/data/tox21.csv",
+ "preds_path": "example/results_gnn.csv",
+ "uncertainty_method": "dropout",
+ "uncertainty_dropout_p": 0.1,
+ "dropout_sampling_size": 10
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/example/train.json b/example/train.json
index 62f2abb4..824d2418 100755
--- a/example/train.json
+++ b/example/train.json
@@ -1,23 +1,25 @@
{
"py/object": "dfpl.options.Options",
- "inputFile": "tests/data/S_dataset.csv",
- "outputDir": "example/results_train/",
- "ecModelDir": "example/results_train/",
- "ecWeightsFile": "random_autoencoder.hdf5",
+ "inputFile": "tests/data/tox21.csv",
+ "outputDir": "example/results/",
+ "ecModelDir": "ae/random_split_autoencoder/encoder_model/",
+ "ecWeightsFile": "",
"verbose": 2,
- "trainAC": true,
- "compressFeatures": true,
+ "trainAC": false,
+ "compressFeatures": false,
"encFPSize": 256,
"aeSplitType": "random",
- "aeEpochs": 2,
"aeBatchSize": 351,
+ "aeEpochs": 2,
"aeOptimizer": "Adam",
"aeActivationFunction": "relu",
"aeLearningRate": 0.001,
- "aeLearningRateDecay": 0.0001,
+ "aeLearningRateDecay": 0.96,
"aeType": "deterministic",
+ "finetuneEncoder": false,
+ "visualizeLatent": false,
"type": "smiles",
"fpType": "topological",
@@ -35,12 +37,9 @@
"fnnType": "FNN",
"optimizer": "Adam",
"lossFunction": "bce",
- "epochs": 11,
- "batchSize": 128,
+ "epochs": 2,
"activationFunction": "selu",
- "dropout": 0.0107,
- "learningRate": 0.0000022,
- "l2reg": 0.001,
+
"aeWabTracking": false,
"wabTracking": false,
diff --git a/example/traingnn.json b/example/traingnn.json
old mode 100644
new mode 100755
index 7a5a0712..b0e4fa96
--- a/example/traingnn.json
+++ b/example/traingnn.json
@@ -1,14 +1,13 @@
{
"py/object": "dfpl.options.GnnOptions",
- "data_path": "tests/data/S_dataset.csv",
- "save_dir": "dmpnn-random/",
- "epochs": 2,
- "num_folds": 2,
+ "data_path": "tests/data/tox21.csv",
+ "save_dir": "dmpnntox21-cv-8020/",
+ "epochs": 30,
"metric": "accuracy",
"loss_function": "binary_cross_entropy",
- "split_type": "random",
+ "split_type": "cv",
"dataset_type": "classification",
"smiles_columns": "smiles",
- "extra_metrics": ["balanced_accuracy","auc","f1","mcc","recall","specificity","precision"],
+ "extra_metrics": ["balanced_accuracy"],
"hidden_size": 256
}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
old mode 100644
new mode 100755
diff --git a/scripts/run-feasible-MoleculeNet-cases.sh b/scripts/run-feasible-MoleculeNet-cases.sh
old mode 100644
new mode 100755
diff --git a/setup.cfg b/setup.cfg
old mode 100644
new mode 100755
diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index 2e76e617..a996f7a3
--- a/setup.py
+++ b/setup.py
@@ -20,16 +20,16 @@
# all packages need for the final usage
# for additional packages during development, use requirements.txt
install_requires=[
+ "protobuf == 3.20.3",
"jsonpickle~=2.1.0",
"matplotlib==3.5.1",
"numpy==1.22.0",
"pandas==1.4.2",
"rdkit-pypi==2022.03.1",
"scikit-learn==1.0.2",
- "keras==2.9.0",
- "tensorflow-gpu==2.9.3",
- "wandb~=0.12.0",
- "umap~=0.1.1",
+ "tensorflow==2.13",
+ "wandb~=0.16.2",
+ "umap-learn~=0.5.3",
"seaborn~=0.12.2",
"chemprop @ git+https://github.com/soulios/chemprop.git@1d73523e49aa28a90b74edc04aaf45d7e124e338",
],
diff --git a/singularity_container/environment.yaml b/singularity_container/environment.yaml
old mode 100644
new mode 100755
diff --git a/tests/data/calibra.csv b/tests/data/calibra.csv
new file mode 100644
index 00000000..7e2963d5
--- /dev/null
+++ b/tests/data/calibra.csv
@@ -0,0 +1,26 @@
+smiles,AR,ER,GR,Aromatase,TR,PPARg,ED
+CN(C)c1ccc(cc1)C(=O)c2ccc(cc2)N(C)C,1,1,1,1,1,1,1
+CC12CCC3C(CCC4=CC(=O)CCC34C)C1CCC2=O,1,1,1,1,1,0,1
+CC12CCC3C(CCc4cc(O)ccc34)C1CCC2O,1,1,1,0,1,1,1
+Oc1c(Br)cc(cc1Br)C#N,1,1,1,0,1,1,1
+Oc1ccc(C=Cc2cc(O)cc(O)c2)cc1,1,1,1,1,0,1,1
+Oc1ccc(cc1)c2ccccc2,1,1,0,1,1,1,1
+CC(=CC1C(C(=O)OCN2C(=O)C3=C(CCCC3)C2=O)C1(C)C)C,1,1,0,1,0,1,1
+CC(=O)OCC(=O)C1(O)C(CC2C3CCC4=CC(=O)C=CC4(C)C3(F)C(O)CC12C)OC(=O)C,1,1,1,1,0,0,1
+CC(=O)OCC(=O)C12N=C(C)OC1CC3C4CCC5=CC(=O)C=CC5(C)C4C(O)CC23C,1,1,1,1,0,0,1
+CC(C)(C)C(=O)OCOC(=O)C1N2C(CC2=O)S(=O)(=O)C1(C)C,1,1,1,0,0,1,1
+CC(C)(c1cc(Cl)c(O)c(Cl)c1)c2cc(Cl)c(O)c(Cl)c2,0,1,1,0,1,1,1
+CC1(C)OC2CC3C4CC(F)C5=CC(=O)CCC5(C)C4C(O)CC3(C)C2(O1)C(=O)CO,1,1,1,1,0,0,1
+CC1(C)OC2CC3C4CCC5=CC(=O)C=CC5(C)C4(F)C(O)CC3(C)C2(O1)C(=O)CO,1,1,1,1,0,0,1
+CC12CCC(=O)C=C1CCC3C2CCC4(C)C3CCC4(O)C#C,1,1,1,0,1,0,1
+CC1CC2C3CC(F)C4=CC(=O)C=CC4(C)C3(F)C(O)CC2(C)C1(O)C(=O)CO,1,1,1,1,0,0,1
+CC1CC2C3CC(F)C4=CC(=O)C=CC4(C)C3(F)C(O)CC2(C)C1(OC(=O)C)C(=O)COC(=O)C,1,1,1,1,0,0,1
+CC1CC2C3CCC(O)(C(=O)CO)C3(C)CC(O)C2C4(C)C=CC(=O)C=C14,1,1,1,1,0,0,1
+CC1CC2C3CCC4=CC(=O)C=CC4(C)C3(F)C(O)CC2(C)C1(O)C(=O)CO,1,1,1,1,0,0,1
+CCC(=O)OC1(C(C)CC2C3CCC4=CC(=O)C=CC4(C)C3(F)C(O)CC12C)C(=O)CCl,1,1,1,1,0,0,1
+CCCC(=O)OC1(CCC2C3CCC4=CC(=O)CCC4(C)C3C(O)CC12C)C(=O)CO,1,1,1,1,0,0,1
+CCN(CC)c1ccc(C(=O)c2ccccc2C(=O)O)c(O)c1,1,1,0,1,0,1,1
+CC[N+](CC)(CC)CCOc1ccc(C=Cc2ccccc2)cc1,1,1,0,1,1,0,1
+O=C(Oc1ccccc1)c2cccc(c2)C(=O)Oc3ccccc3,1,1,0,1,1,0,1
+SCC(=O)OCCOC(=O)CS,1,0,1,1,1,0,1
+C(CSc1ccccc1)Sc2ccccc2,1,1,0,1,0,0,1
\ No newline at end of file
diff --git a/tests/data/inchi.tsv b/tests/data/inchi.tsv
deleted file mode 100644
index bfaccf13..00000000
--- a/tests/data/inchi.tsv
+++ /dev/null
@@ -1,5 +0,0 @@
-DTXSID7020001 InChI=1S/C11H9N3/c12-10-6-5-8-7-3-1-2-4-9(7)13-11(8)14-10/h1-6H,(H3,12,13,14) FJTNLJLPLJDTRM-UHFFFAOYSA-N
-DTXSID5039224 InChI=1S/C2H4O/c1-2-3/h2H,1H3 IKHGUXGNUITLKF-UHFFFAOYSA-N
-DTXSID50872971 InChI=1S/C4H8N2O/c1-3-5-6(2)4-7/h3-4H,1-2H3/b5-3+ IMAGWKUTFZRWSB-HWKANZROSA-N
-DTXSID2020004 InChI=1S/C2H5NO/c1-2-3-4/h2,4H,1H3/b3-2+ FZENGILVLUJGJX-NSCUHMNNSA-N
-DTXSID7020005 InChI=1S/C2H5NO/c1-2(3)4/h1H3,(H2,3,4) DLFVBJFMPXGRIB-UHFFFAOYSA-N
\ No newline at end of file
diff --git a/tests/data/smiles.csv b/tests/data/smiles.csv
index 9383afdd..b965e11e 100644
--- a/tests/data/smiles.csv
+++ b/tests/data/smiles.csv
@@ -1,7 +1,7 @@
-smiles
-CN(C)c1ccc(cc1)C(=O)c2ccc(cc2)N(C)C
-CC12CCC3C(CCC4=CC(=O)CCC34C)C1CCC2=O
-CC12CCC3C(CCc4cc(O)ccc34)C1CCC2O
-Oc1c(Br)cc(cc1Br)C#N
-Oc1ccc(C=Cc2cc(O)cc(O)c2)cc1
-Oc1ccc(cc1)c2ccccc2
\ No newline at end of file
+smiles,ID
+CN(C)c1ccc(cc1)C(=O)c2ccc(cc2)N(C)C,2982
+CC12CCC3C(CCC4=CC(=O)CCC34C)C1CCC2=O,4345435
+CC12CCC3C(CCc4cc(O)ccc34)C1CCC2O,42121
+Oc1c(Br)cc(cc1Br)C#N,421421
+Oc1ccc(C=Cc2cc(O)cc(O)c2)cc1,2142143
+Oc1ccc(cc1)c2ccccc2,2413213
\ No newline at end of file
diff --git a/tests/generic_encoder/keras_metadata.pb b/tests/generic_encoder/keras_metadata.pb
new file mode 100755
index 00000000..16312434
--- /dev/null
+++ b/tests/generic_encoder/keras_metadata.pb
@@ -0,0 +1,6 @@
+
+ß%root"_tf_keras_network*½%{"name": "model_1", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "must_restore_from_config": false, "class_name": "Functional", "config": {"name": "model_1", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 2048]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": []}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1024, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 512, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_1", "inbound_nodes": [[["dense", 0, 0, {}]]]}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 256, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_2", "inbound_nodes": [[["dense_1", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense_2", 0, 0]]}, "shared_object_id": 10, "input_spec": [{"class_name": "InputSpec", "config": {"dtype": null, "shape": {"class_name": "__tuple__", "items": [null, 2048]}, "ndim": 2, "max_ndim": null, "min_ndim": null, "axes": {}}}], "build_input_shape": {"class_name": "TensorShape", "items": [null, 2048]}, "is_graph_network": true, "full_save_spec": {"class_name": "__tuple__", "items": [[{"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 2048]}, "float32", "input_1"]}], {}]}, "save_spec": {"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 2048]}, "float32", "input_1"]}, "keras_version": "2.6.0", "backend": "tensorflow", "model_config": {"class_name": "Functional", "config": {"name": "model_1", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 2048]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "name": "input_1", "inbound_nodes": [], "shared_object_id": 0}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1024, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense", "inbound_nodes": [[["input_1", 0, 0, {}]]], "shared_object_id": 3}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 512, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_1", "inbound_nodes": [[["dense", 0, 0, {}]]], "shared_object_id": 6}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 256, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "name": "dense_2", "inbound_nodes": [[["dense_1", 0, 0, {}]]], "shared_object_id": 9}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense_2", 0, 0]]}}}2
+üroot.layer-0"_tf_keras_input_layer*Ì{"class_name": "InputLayer", "name": "input_1", "dtype": "float32", "sparse": false, "ragged": false, "batch_input_shape": {"class_name": "__tuple__", "items": [null, 2048]}, "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 2048]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}}2
+ñroot.layer_with_weights-0"_tf_keras_layer*º{"name": "dense", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1024, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}, "shared_object_id": 1}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 2}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["input_1", 0, 0, {}]]], "shared_object_id": 3, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 2048}}, "shared_object_id": 12}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 2048]}}2
+òroot.layer_with_weights-1"_tf_keras_layer*»{"name": "dense_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 512, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense", 0, 0, {}]]], "shared_object_id": 6, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 1024}}, "shared_object_id": 13}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 1024]}}2
+òroot.layer_with_weights-2"_tf_keras_layer*»{"name": "dense_2", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 256, "activation": "selu", "use_bias": true, "kernel_initializer": {"class_name": "LecunNormal", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_1", 0, 0, {}]]], "shared_object_id": 9, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 512}}, "shared_object_id": 14}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 512]}}2
\ No newline at end of file
diff --git a/tests/generic_encoder/saved_model.pb b/tests/generic_encoder/saved_model.pb
new file mode 100755
index 00000000..dca9f9b8
Binary files /dev/null and b/tests/generic_encoder/saved_model.pb differ
diff --git a/tests/generic_encoder/variables/variables.data-00000-of-00001 b/tests/generic_encoder/variables/variables.data-00000-of-00001
new file mode 100755
index 00000000..db0b5cb2
Binary files /dev/null and b/tests/generic_encoder/variables/variables.data-00000-of-00001 differ
diff --git a/tests/generic_encoder/variables/variables.index b/tests/generic_encoder/variables/variables.index
new file mode 100755
index 00000000..d14011f8
Binary files /dev/null and b/tests/generic_encoder/variables/variables.index differ
diff --git a/tests/run_autoencoder.py b/tests/run_autoencoder.py
old mode 100644
new mode 100755
diff --git a/tests/run_fnntraining.py b/tests/run_fnntraining.py
old mode 100644
new mode 100755
index 4146ad4a..40f9287a
--- a/tests/run_fnntraining.py
+++ b/tests/run_fnntraining.py
@@ -24,7 +24,7 @@
testSize=0.2,
kFolds=1,
verbose=2,
- trainAC=False,
+ trainAC=True,
trainFNN=True,
)
@@ -49,11 +49,7 @@ def run_single_label_training(opts: opt.Options) -> None:
if opts.trainAC:
logging.info("Training autoencoder")
- encoder = ac.train_full_ac(df, opts)
- # encoder.save_weights(opts.acFile)
- else:
- logging.info("Using trained autoencoder")
- (_, encoder) = ac.define_ac_model(opts)
+ encoder, train_indices, test_indices = ac.train_full_ac(df, opts)
df = ac.compress_fingerprints(df, encoder)
diff --git a/tests/run_predictgnn.py b/tests/run_predictgnn.py
old mode 100644
new mode 100755
index 979c2868..addebf52
--- a/tests/run_predictgnn.py
+++ b/tests/run_predictgnn.py
@@ -1,9 +1,8 @@
import logging
-import os
import pathlib
-import pandas as pd
-from chemprop import args, train
+import chemprop as cp
+from chemprop import train
import dfpl.options as opt
import dfpl.utils as utils
@@ -26,26 +25,10 @@ def test_predictdmpnn(opts: opt.GnnOptions) -> None:
)
json_arg_path = utils.makePathAbsolute(f"{example_directory}/predictgnn.json")
- ignore_elements = [
- "py/object",
- "checkpoint_paths",
- "save_dir",
- "saving_name",
- ]
- arguments, data = utils.createArgsFromJson(
- json_arg_path, ignore_elements, return_json_object=True
- )
- arguments.append("--preds_path")
- arguments.append("")
- save_dir = data.get("save_dir")
- name = data.get("saving_name")
-
- opts = args.PredictArgs().parse_args(arguments)
- opts.preds_path = os.path.join(save_dir, name)
- df = pd.read_csv(opts.test_path)
- smiles = [[row.smiles] for _, row in df.iterrows()]
+ arguments = utils.createArgsFromJson(json_arg_path)
+ opts = cp.args.PredictArgs().parse_args(arguments)
- train.make_predictions(args=opts, smiles=smiles)
+ train.make_predictions(args=opts)
print("predictdmpnn test complete.")
diff --git a/tests/run_prediction.py b/tests/run_prediction.py
old mode 100644
new mode 100755
index cb7d1fea..db111aa0
--- a/tests/run_prediction.py
+++ b/tests/run_prediction.py
@@ -2,6 +2,8 @@
import pathlib
from os import path
+from tensorflow.keras.models import load_model
+
import dfpl.autoencoder as ac
import dfpl.fingerprint as fp
import dfpl.options as opt
@@ -12,14 +14,14 @@
test_predict_args = opt.Options(
inputFile=f"{project_directory}/data/smiles.csv",
outputDir=f"{project_directory}/preds/",
- ecModelDir=utils.makePathAbsolute(f"{project_directory}/output/"),
- ecWeightsFile=utils.makePathAbsolute(
- f"{project_directory}/output/D_datasetdeterministicrandom.autoencoder.weightsrandom.autoencoder.weights.hdf5"
- ),
+ # ecModelDir=utils.makePathAbsolute(
+ # f"{project_directory}/data/random_split_autoencoder/encoder_model/"
+ # ),
fnnModelDir=f"{project_directory}/output/fnnTrainingCompressed/AR_saved_model",
fpSize=2048,
type="smiles",
fpType="topological",
+ compressFeatures=False,
)
@@ -36,16 +38,13 @@ def test_predictions(opts: opt.Options):
)
# use_compressed = False
- if opts.ecWeightsFile:
+ if opts.ecModelDir:
# use_compressed = True
# load trained model for autoencoder
(autoencoder, encoder) = ac.define_ac_model(opts, output_bias=None)
- autoencoder.load_weights(opts.ecWeightsFile)
+ encoder = load_model(opts.ecModelDir)
# compress the fingerprints using the autoencoder
df = ac.compress_fingerprints(df, encoder)
- # model = tensorflow.keras.models.load_model(opts.fnnModelDir, compile=False)
- # model.compile(loss=opts.lossFunction, optimizer=opts.optimizer)
- # predict
df2 = p.predict_values(df=df, opts=opts)
names_columns = [c for c in df2.columns if c not in ["fp", "fpcompressed"]]
diff --git a/tests/run_traingnn.py b/tests/run_traingnn.py
old mode 100644
new mode 100755
diff --git a/tests/run_vae.py b/tests/run_vae.py
old mode 100644
new mode 100755
diff --git a/tests/test_fingerprint.py b/tests/test_fingerprint.py
old mode 100644
new mode 100755
diff --git a/tests/test_fractional_sampling.py b/tests/test_fractional_sampling.py
old mode 100644
new mode 100755
diff --git a/tests/try_fpcomparison.py b/tests/try_fpcomparison.py
old mode 100644
new mode 100755