A benchmark dataset and interpretable deep learning framework for drug-induced developmental neurotoxicity prediction.
The 13 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
- [1] § Materials and methods › Model construction and training strategy › Model training and evaluation ↔ 3-DNN_MACCS_modeling/DNN-test_selection.py, lines 33–67 · score 0.70 · monitoring validation, model selection, cross validation, weights, optimal, epochs
- [2] § Materials and methods › Performance metrics and statistical analysis ↔ 3-DNN_MACCS_modeling/DNN-independent validation.py, lines 208–293 · score 0.69 · F1 score, Matthew, TN, TP, FN, precision
- [3] § Materials and methods › Performance metrics and statistical analysis ↔ 3-DNN_MACCS_modeling/DNN-test_selection.py, lines 560–599 · score 0.67 · predicted toxic, evaluation metrics, F1 score, precision, recall, accuracy
- [4] § Materials and methods › Model construction and training strategy › Model training and evaluation ↔ 3-DNN_MACCS_modeling/DNN-training-5-fold-CV.py, lines 34–65 · score 0.65 · fold cross validation, network architectures, overfitting, epochs, training, Model
- [5] § Results and discussion › Analysis of the training stability and generalization ability of the DNN_MACCS model ↔ 3-DNN_MACCS_modeling/DNN-test_selection.py, lines 889–968 · score 0.64 · developmental neurotoxicity prediction, validation AUC, F1 score, dnn maccs, overfitting, configuration
- [6] § Materials and methods › Model interpretability analysis ↔ 4-applicability_domain_SHAP_analysis/SHAP-analysisl.py, lines 1056–1121 · score 0.62 · SHapley, exPlanations, Model interpretability, Additive, global, transparency
- [7] § Materials and methods › Benchmark dataset preparation › Data partitioning of the benchmark dataset ↔ 1-Benchmark_Dataset_Preparation/plot_umap_chemical_space.py, the whole file · a weak match · score 0.59 · chemical space, DNT positive, UMAP, benchmark, SMILES, fingerprints
- [8] § Results and discussion › Strengths and limitations ↔ 4-applicability_domain_SHAP_analysis/Application_Domain_UMAP_tSNE.py, lines 429–462 · score 0.58 · broader chemical, applicability domain, experimentally validated, model generalizability, chemical space, expand
- [9] § Results and discussion › Analysis of the training stability and generalization ability of the DNN_MACCS model ↔ 3-DNN_MACCS_modeling/DNN-test_selection.py, lines 889–968 · score 0.56 · potential overfitting, best validation, dnn maccs, loss, epochs, accuracy
- [10] § Materials and methods › Benchmark dataset preparation › Preprocessing of positive samples ↔ 1-Benchmark_Dataset_Preparation/DNTREF_curation.py, lines 25–46 · score 0.54 · heavy atoms, mixtures, parent, curation, SMILES
- [11] § Materials and methods › Chemical space visualization and applicability domain analysis ↔ 4-applicability_domain_SHAP_analysis/Application_Domain_UMAP_tSNE.py, lines 198–201 · score 0.52 · Dimensionality reduction, UMAP, Neighbor, SNE, domain
- [12] § Materials and methods › Benchmark dataset preparation ↔ 3-DNN_MACCS_modeling/DNN-test_selection.py, lines 1014–1134 · score 0.52 · model training, developmental neurotoxicity prediction, pipeline, selection, preprocessing
- [13] § Materials and methods › Benchmark dataset preparation › Preprocessing of presumed negative samples and construction of a balanced benchmark dataset ↔ 1-Benchmark_Dataset_Preparation/hard_negative_matching.py, lines 54–90 · score 0.50 · hard negative matching, greedy, MW, log, benchmark
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 1,147 lines · 51 KB · no license · 5 matches
- """
- Developmental Neurotoxicity Prediction DNN QSAR Model
- Author: DeepSeek Assistant
- Function: Train DNN model using validation accuracy for model selection
- Input files: DNT_benchmark_random_train.csv, DNT_benchmark_random_val.csv
- Output: Trained model, performance evaluation results, visualization charts
- """
- import numpy as np
- import pandas as pd
- import matplotlib
- matplotlib.use('TkAgg') # Set backend to avoid GUI issues
- import matplotlib.pyplot as plt
- import seaborn as sns
- from sklearn.metrics import (roc_auc_score, accuracy_score, precision_score,
- recall_score, f1_score, confusion_matrix,
- roc_curve, classification_report)
- from sklearn.preprocessing import StandardScaler
- from rdkit import Chem
- from rdkit.Chem import MACCSkeys
- import tensorflow as tf
- from tensorflow import keras
- from tensorflow.keras import layers, models, optimizers, regularizers, callbacks
- import warnings
- warnings.filterwarnings('ignore')
- import os
- # Set random seeds for reproducibility
- SEED = 42
- np.random.seed(SEED)
- tf.random.set_seed(SEED)
- # ==================== Configuration ====================
- class Config:
- """Model configuration parameters"""
- # Optimal parameters (based on previous cross-validation results)
- OPTIMAL_PARAMS = {
- 'layer_sizes': [256, 128, 64],
- 'dropout_rate': 0.4,
- 'learning_rate': 0.0005,
- 'l2_reg': 0.001,
- 'batch_size': 32,
- 'epochs': 200 # Slightly reduced for practical training time
- }
- # Training parameters
- REDUCE_LR_PATIENCE = 20
- REDUCE_LR_FACTOR = 0.5
- MIN_LR = 1e-6
- # Early stopping parameters
- EARLY_STOPPING_MONITOR = 'val_accuracy' # Monitor validation accuracy
- EARLY_STOPPING_PATIENCE = 40 # Stop if no improvement for 40 epochs
- EARLY_STOPPING_MIN_DELTA = 0.001 # Minimum change to qualify as improvement
- EARLY_STOPPING_MODE = 'max' # Maximize validation accuracy
- EARLY_STOPPING_RESTORE_BEST_WEIGHTS = True # Restore best weights when stopped
- # Model selection
- MODEL_SELECTION_METRIC = 'val_accuracy' # Use validation accuracy for model selection
- MODEL_SELECTION_MODE = 'max' # Maximize validation accuracy
- # Visualization settings
- FIGURE_DPI = 300
- # File paths
- TRAIN_FILE = 'DNT_benchmark_random_train-4_split_train.csv'
- VAL_FILE = 'DNT_benchmark_random_train-4_split_test.csv'
- # ==================== Data Preprocessing ====================
- class DataProcessor:
- """Data preprocessing class"""
- @staticmethod
- def load_data(train_path, val_path):
- """Load training and validation datasets"""
- try:
- train_df = pd.read_csv(train_path)
- val_df = pd.read_csv(val_path)
- print("=" * 60)
- print("Data Loading Complete")
- print("=" * 60)
- print(f"Training set size: {len(train_df)}")
- print(f"Validation set size: {len(val_df)}")
- print(f"\nTraining set class distribution:")
- print(train_df['label'].value_counts())
- print(f"\nValidation set class distribution:")
- print(val_df['label'].value_counts())
- if 'source' in train_df.columns:
- print(f"\nTraining set source distribution:")
- print(train_df['source'].value_counts())
- return train_df, val_df
- except Exception as e:
- print(f"Failed to load data: {e}")
- raise
- @staticmethod
- def smiles_to_maccs(smiles):
- """Convert SMILES to MACCS fingerprints"""
- try:
- mol = Chem.MolFromSmiles(smiles)
- if mol is not None:
- fp = MACCSkeys.GenMACCSKeys(mol)
- return np.array(fp)
- else:
- return np.zeros(167)
- except:
- return np.zeros(167)
- def prepare_features(self, train_df, val_df):
- """Prepare feature matrices and label vectors"""
- print("\nGenerating MACCS fingerprint features...")
- # Generate MACCS fingerprints
- X_train = np.array([self.smiles_to_maccs(s) for s in train_df['smiles']])
- y_train = train_df['label'].values
- X_val = np.array([self.smiles_to_maccs(s) for s in val_df['smiles']])
- y_val = val_df['label'].values
- # Check feature dimensions
- print(f"Training set feature dimension: {X_train.shape}")
- print(f"Validation set feature dimension: {X_val.shape}")
- # Data standardization
- scaler = StandardScaler()
- X_train_scaled = scaler.fit_transform(X_train)
- X_val_scaled = scaler.transform(X_val)
- print("Feature standardization complete")
- return X_train_scaled, y_train, X_val_scaled, y_val, scaler
- # ==================== DNN Model Builder ====================
- class DNNModelBuilder:
- """DNN model builder"""
- @staticmethod
- def create_model(input_dim=167,
- layer_sizes=[256, 128, 64],
- dropout_rate=0.3,
- learning_rate=0.001,
- l2_reg=0.001):
- """Create DNN model architecture"""
- model = models.Sequential()
- # Input layer and first hidden layer
- model.add(layers.Dense(layer_sizes[0], input_dim=input_dim,
- activation='relu',
- kernel_regularizer=regularizers.l2(l2_reg),
- name=f"dense_input_{layer_sizes[0]}"))
- model.add(layers.BatchNormalization(name=f"bn_1"))
- model.add(layers.Dropout(dropout_rate, name=f"dropout_1"))
- # Add subsequent hidden layers
- for i, layer_size in enumerate(layer_sizes[1:], start=2):
- model.add(layers.Dense(layer_size, activation='relu',
- kernel_regularizer=regularizers.l2(l2_reg),
- name=f"dense_hidden_{i}_{layer_size}"))
- model.add(layers.BatchNormalization(name=f"bn_{i}"))
- model.add(layers.Dropout(dropout_rate, name=f"dropout_{i}"))
- # Output layer (binary classification)
- model.add(layers.Dense(1, activation='sigmoid', name="output"))
- # Compile model
- optimizer = optimizers.Adam(learning_rate=learning_rate)
- model.compile(loss='binary_crossentropy',
- optimizer=optimizer,
- metrics=['accuracy',
- keras.metrics.AUC(name='auc'),
- keras.metrics.Precision(name='precision'),
- keras.metrics.Recall(name='recall')])
- return model
- # ==================== Model Trainer ====================
- class ModelTrainer:
- """Model trainer with validation accuracy selection and early stopping"""
- def __init__(self, config):
- self.config = config
- self.best_epoch = 0
- self.best_val_accuracy = 0.0
- self.best_model_path = None
- self.early_stopped_epoch = None
- self.stopped_reason = None
- def train_final_model(self, X_train, y_train, X_val, y_val):
- """Train final model using optimal parameters with validation accuracy selection and early stopping"""
- print("\n" + "=" * 60)
- print("Training Final Model with Optimal Parameters")
- print("=" * 60)
- print("Model Selection Strategy: Using validation accuracy to select best model")
- print("Early Stopping: Enabled with patience =", self.config.EARLY_STOPPING_PATIENCE)
- print("=" * 60)
- # Get optimal parameters
- optimal_params = self.config.OPTIMAL_PARAMS
- # Separate model building parameters and training parameters
- model_build_params = {
- 'layer_sizes': optimal_params['layer_sizes'],
- 'dropout_rate': optimal_params['dropout_rate'],
- 'learning_rate': optimal_params['learning_rate'],
- 'l2_reg': optimal_params['l2_reg']
- }
- # Create final model
- final_model = DNNModelBuilder.create_model(
- input_dim=X_train.shape[1],
- **model_build_params
- )
- # Print model architecture
- print("\nModel Architecture:")
- final_model.summary()
- # Create directory for saved models
- os.makedirs('checkpoints', exist_ok=True)
- self.best_model_path = 'checkpoints/best_model_val_acc.h5'
- # Training callbacks - focus on validation accuracy with early stopping
- callbacks_list = [
- # Early stopping callback
- callbacks.EarlyStopping(
- monitor=self.config.EARLY_STOPPING_MONITOR,
- patience=self.config.EARLY_STOPPING_PATIENCE,
- min_delta=self.config.EARLY_STOPPING_MIN_DELTA,
- mode=self.config.EARLY_STOPPING_MODE,
- restore_best_weights=self.config.EARLY_STOPPING_RESTORE_BEST_WEIGHTS,
- verbose=1
- ),
- # Learning rate reduction
- callbacks.ReduceLROnPlateau(
- monitor='val_loss',
- factor=self.config.REDUCE_LR_FACTOR,
- patience=self.config.REDUCE_LR_PATIENCE,
- min_lr=self.config.MIN_LR,
- verbose=1
- ),
- # Model checkpoint - save best model based on validation accuracy
- callbacks.ModelCheckpoint(
- self.best_model_path,
- monitor=self.config.MODEL_SELECTION_METRIC,
- mode=self.config.MODEL_SELECTION_MODE,
- save_best_only=True,
- save_weights_only=False,
- verbose=1
- ),
- callbacks.CSVLogger('training_log.csv'),
- # Custom callback to track best epoch
- callbacks.LambdaCallback(
- on_epoch_end=lambda epoch, logs: self._update_best_epoch(epoch, logs)
- ),
- # Custom callback to track early stopping
- callbacks.LambdaCallback(
- on_epoch_end=lambda epoch, logs: self._check_early_stopping(epoch, logs)
- )
- ]
- # Train model
- print(f"\nTraining Configuration:")
- print(f" Network Architecture: {model_build_params['layer_sizes']}")
- print(f" Dropout Rate: {model_build_params['dropout_rate']}")
- print(f" Learning Rate: {model_build_params['learning_rate']}")
- print(f" L2 Regularization: {model_build_params['l2_reg']}")
- print(f" Batch Size: {optimal_params['batch_size']}")
- print(f" Maximum Epochs: {optimal_params['epochs']}")
- print(f" Early Stopping Patience: {self.config.EARLY_STOPPING_PATIENCE}")
- print(f" Model Selection Metric: {self.config.MODEL_SELECTION_METRIC}")
- print(f" Best Model Saved To: {self.best_model_path}")
- history = final_model.fit(
- X_train, y_train,
- validation_data=(X_val, y_val),
- epochs=optimal_params['epochs'],
- batch_size=optimal_params['batch_size'],
- callbacks=callbacks_list,
- verbose=1
- )
- # Check if early stopping occurred
- if hasattr(final_model, 'history') and len(final_model.history.history['loss']) < optimal_params['epochs']:
- self.early_stopped_epoch = len(final_model.history.history['loss'])
- self.stopped_reason = f"Early stopping triggered at epoch {self.early_stopped_epoch}"
- print(f"\n⚠ Early stopping was triggered at epoch {self.early_stopped_epoch}")
- print(f" Best validation accuracy: {self.best_val_accuracy:.4f}")
- # Load the best model based on validation accuracy
- print(f"\nLoading best model from epoch {self.best_epoch + 1}")
- print(f"Best validation accuracy: {self.best_val_accuracy:.4f}")
- # Create a new model instance and load the best weights
- best_model = DNNModelBuilder.create_model(
- input_dim=X_train.shape[1],
- **model_build_params
- )
- best_model.load_weights(self.best_model_path)
- return best_model, history, final_model
- def _update_best_epoch(self, epoch, logs):
- """Update the best epoch based on validation accuracy"""
- if logs is not None and 'val_accuracy' in logs:
- current_val_acc = logs['val_accuracy']
- if current_val_acc > self.best_val_accuracy:
- self.best_val_accuracy = current_val_acc
- self.best_epoch = epoch
- print(f"\nNew best validation accuracy: {current_val_acc:.4f} at epoch {epoch + 1}")
- def _check_early_stopping(self, epoch, logs):
- """Check if early stopping criteria are met"""
- # This is handled by Keras EarlyStopping callback
- pass
- @staticmethod
- def plot_training_history(history, params, best_epoch=None, best_val_acc=None):
- """Plot training history with best epoch marked and y-axis starting from 0"""
- plt.figure(figsize=(14, 5))
- # Subplot 1: Loss (y-axis starts from 0)
- plt.subplot(1, 3, 1)
- plt.plot(history.history['loss'], label='Training Loss')
- plt.plot(history.history['val_loss'], label='Validation Loss')
- # Mark best epoch if provided
- if best_epoch is not None:
- plt.axvline(x=best_epoch, color='r', linestyle='--', alpha=0.7,
- label=f'Best Epoch: {best_epoch + 1}')
- plt.plot(best_epoch, history.history['val_loss'][best_epoch],
- 'ro', markersize=8)
- plt.title('Model Loss')
- plt.xlabel('Epochs')
- plt.ylabel('Loss')
- plt.legend()
- plt.grid(True, alpha=0.3)
- plt.ylim(bottom=0) # Set y-axis to start from 0
- # Subplot 2: AUC (y-axis starts from 0)
- plt.subplot(1, 3, 2)
- plt.plot(history.history['auc'], label='Training AUC')
- plt.plot(history.history['val_auc'], label='Validation AUC')
- # Mark best epoch if provided
- if best_epoch is not None:
- plt.axvline(x=best_epoch, color='r', linestyle='--', alpha=0.7)
- plt.plot(best_epoch, history.history['val_auc'][best_epoch],
- 'ro', markersize=8)
- plt.title('Model AUC')
- plt.xlabel('Epochs')
- plt.ylabel('AUC')
- plt.legend()
- plt.grid(True, alpha=0.3)
- plt.ylim(0, 1) # Set y-axis from 0 to 1
- # Subplot 3: Accuracy (y-axis starts from 0)
- plt.subplot(1, 3, 3)
- plt.plot(history.history['accuracy'], label='Training Accuracy')
- plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
- # Mark best epoch if provided
- if best_epoch is not None:
- plt.axvline(x=best_epoch, color='r', linestyle='--', alpha=0.7)
- plt.plot(best_epoch, history.history['val_accuracy'][best_epoch],
- 'ro', markersize=8, label=f'Best: {best_val_acc:.4f}')
- plt.title('Model Accuracy')
- plt.xlabel('Epochs')
- plt.ylabel('Accuracy')
- plt.legend()
- plt.grid(True, alpha=0.3)
- plt.ylim(0, 1) # Set y-axis from 0 to 1
- title_suffix = ""
- if best_epoch is not None:
- title_suffix = f" | Best Epoch: {best_epoch + 1}"
- plt.suptitle(f"Training History - Network Architecture: {params['layer_sizes']}{title_suffix}",
- fontsize=14)
- plt.tight_layout()
- plt.savefig('training_history_val_acc.png', dpi=Config.FIGURE_DPI, bbox_inches='tight')
- plt.show()
- # ==================== Model Evaluator ====================
- class ModelEvaluator:
- """Model evaluator"""
- @staticmethod
- def evaluate_model(model, X_train, y_train, X_val, y_val, model_name="Final Model", val_df=None):
- """Evaluate model performance and output statistical results"""
- print("\n" + "=" * 60)
- print(f"Model Performance Evaluation - {model_name}")
- print("=" * 60)
- # Training set predictions
- y_train_pred = model.predict(X_train, verbose=0)
- y_train_pred_class = (y_train_pred > 0.5).astype(int)
- y_train_pred = y_train_pred.flatten()
- # Validation set predictions
- y_val_pred = model.predict(X_val, verbose=0)
- y_val_pred_class = (y_val_pred > 0.5).astype(int)
- y_val_pred = y_val_pred.flatten()
- # Calculate metrics
- train_metrics = ModelEvaluator._calculate_metrics(y_train, y_train_pred, y_train_pred_class)
- val_metrics = ModelEvaluator._calculate_metrics(y_val, y_val_pred, y_val_pred_class)
- # Output performance metrics
- metrics_df = ModelEvaluator._create_metrics_table(train_metrics, val_metrics)
- # Output classification report
- ModelEvaluator._print_classification_report(y_val, y_val_pred_class)
- # Save validation set predictions if val_df is provided
- if val_df is not None:
- ModelEvaluator._save_validation_predictions(val_df, y_val, y_val_pred, y_val_pred_class, model_name)
- return metrics_df, y_train_pred, y_val_pred, y_val_pred_class
- @staticmethod
- def _calculate_metrics(y_true, y_pred_prob, y_pred_class):
- """Calculate all evaluation metrics"""
- return {
- 'Accuracy': accuracy_score(y_true, y_pred_class),
- 'Precision': precision_score(y_true, y_pred_class, zero_division=0),
- 'Recall': recall_score(y_true, y_pred_class, zero_division=0),
- 'F1-Score': f1_score(y_true, y_pred_class, zero_division=0),
- 'AUC': roc_auc_score(y_true, y_pred_prob),
- 'Specificity': ModelEvaluator._calculate_specificity(y_true, y_pred_class)
- }
- @staticmethod
- def _calculate_specificity(y_true, y_pred):
- """Calculate specificity"""
- tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
- return tn / (tn + fp) if (tn + fp) > 0 else 0
- @staticmethod
- def _create_metrics_table(train_metrics, val_metrics):
- """Create performance metrics table"""
- metrics_df = pd.DataFrame({
- 'Training Set': train_metrics,
- 'Validation Set': val_metrics
- })
- print("\nPerformance Metrics Comparison:")
- print(metrics_df.round(4))
- # Calculate differences
- metrics_df['Difference (Validation-Training)'] = metrics_df['Validation Set'] - metrics_df['Training Set']
- print("\nTraining vs Validation Set Differences:")
- print(metrics_df['Difference (Validation-Training)'].round(4))
- return metrics_df
- @staticmethod
- def _print_classification_report(y_true, y_pred):
- """Print classification report"""
- print("\nValidation Set Classification Report:")
- print(classification_report(y_true, y_pred,
- target_names=['Non-toxic (Class 0)', 'Toxic (Class 1)']))
- @staticmethod
- def _save_validation_predictions(val_df, y_true, y_pred_prob, y_pred_class, model_name):
- """Save validation set predictions to CSV file"""
- try:
- # Create a DataFrame with predictions
- predictions_df = pd.DataFrame({
- 'smiles': val_df['smiles'].values,
- 'true_label': y_true,
- 'predicted_probability': y_pred_prob,
- 'predicted_label': y_pred_class,
- 'correct_prediction': (y_true == y_pred_class).astype(int)
- })
- # Add prediction confidence categories
- predictions_df['prediction_confidence'] = pd.cut(
- predictions_df['predicted_probability'],
- bins=[0, 0.3, 0.7, 1.0],
- labels=['Low', 'Medium', 'High'],
- include_lowest=True
- )
- # Sort by prediction probability (descending)
- predictions_df = predictions_df.sort_values('predicted_probability', ascending=False)
- # Create model-specific filename
- model_name_clean = model_name.lower().replace(' ', '_').replace('-', '_')
- filename = f'validation_predictions_{model_name_clean}.csv'
- # Save to CSV
- predictions_df.to_csv(filename, index=False)
- print(f"\n✓ Validation set predictions saved to: {filename}")
- print(f" - Total molecules: {len(predictions_df)}")
- print(f" - Correct predictions: {predictions_df['correct_prediction'].sum()} ({predictions_df['correct_prediction'].mean():.2%})")
- # Print summary statistics
- print(f"\n Prediction Statistics:")
- print(f" - Mean predicted probability: {predictions_df['predicted_probability'].mean():.4f}")
- print(f" - Std predicted probability: {predictions_df['predicted_probability'].std():.4f}")
- print(f" - Min predicted probability: {predictions_df['predicted_probability'].min():.4f}")
- print(f" - Max predicted probability: {predictions_df['predicted_probability'].max():.4f}")
- # Count by confidence level
- confidence_counts = predictions_df['prediction_confidence'].value_counts().sort_index()
- print(f"\n Confidence Level Distribution:")
- for conf_level, count in confidence_counts.items():
- percentage = count / len(predictions_df) * 100
- print(f" - {conf_level}: {count} molecules ({percentage:.1f}%)")
- except Exception as e:
- print(f"⚠ Warning: Failed to save validation predictions: {e}")
- @staticmethod
- def plot_comparison_visualizations(y_train, y_train_pred_prob_best, y_train_pred_prob_final,
- y_val, y_val_pred_prob_best, y_val_pred_prob_final,
- y_val_true, y_val_pred_class_best,
- train_metrics_best, val_metrics_best,
- train_metrics_final=None, val_metrics_final=None):
- """Plot comparison visualizations between best and final models"""
- fig = plt.figure(figsize=(16, 10))
- # Subplot 1: ROC Curves Comparison
- ax1 = plt.subplot(2, 3, 1)
- # Calculate ROC curves for best model
- fpr_train_best, tpr_train_best, _ = roc_curve(y_train, y_train_pred_prob_best)
- fpr_val_best, tpr_val_best, _ = roc_curve(y_val, y_val_pred_prob_best)
- ax1.plot(fpr_train_best, tpr_train_best, 'b-',
- label=f'Training (Best) AUC = {train_metrics_best["AUC"]:.3f}')
- ax1.plot(fpr_val_best, tpr_val_best, 'r-',
- label=f'Validation (Best) AUC = {val_metrics_best["AUC"]:.3f}')
- # Calculate ROC curves for final model if provided
- if y_train_pred_prob_final is not None and y_val_pred_prob_final is not None:
- fpr_train_final, tpr_train_final, _ = roc_curve(y_train, y_train_pred_prob_final)
- fpr_val_final, tpr_val_final, _ = roc_curve(y_val, y_val_pred_prob_final)
- ax1.plot(fpr_train_final, tpr_train_final, 'b--', alpha=0.7,
- label=f'Training (Final) AUC = {train_metrics_final["AUC"]:.3f}')
- ax1.plot(fpr_val_final, tpr_val_final, 'r--', alpha=0.7,
- label=f'Validation (Final) AUC = {val_metrics_final["AUC"]:.3f}')
- ax1.plot([0, 1], [0, 1], 'k--', label='Random Classifier')
- ax1.set_xlim([0.0, 1.0])
- ax1.set_ylim([0.0, 1.05])
- ax1.set_xlabel('False Positive Rate (FPR)')
- ax1.set_ylabel('True Positive Rate (TPR)')
- ax1.set_title('ROC Curve Comparison')
- ax1.legend(loc="lower right")
- ax1.grid(True, alpha=0.3)
- # Subplot 2: Confusion Matrix for Best Model
- ax2 = plt.subplot(2, 3, 2)
- cm_best = confusion_matrix(y_val_true, y_val_pred_class_best)
- sns.heatmap(cm_best, annot=True, fmt='d', cmap='Blues', ax=ax2,
- xticklabels=['Predicted Non-toxic', 'Predicted Toxic'],
- yticklabels=['Actual Non-toxic', 'Actual Toxic'])
- ax2.set_title('Confusion Matrix (Best Model)')
- ax2.set_ylabel('True Label')
- ax2.set_xlabel('Predicted Label')
- # Subplot 3: Metrics Comparison Bar Chart
- ax3 = plt.subplot(2, 3, 3)
- metrics_to_plot = ['Accuracy', 'Precision', 'Recall', 'F1-Score', 'AUC']
- x = np.arange(len(metrics_to_plot))
- width = 0.35
- train_values_best = [train_metrics_best[m] for m in metrics_to_plot]
- val_values_best = [val_metrics_best[m] for m in metrics_to_plot]
- bars1 = ax3.bar(x - width/2, train_values_best, width, label='Training (Best)', alpha=0.8, color='blue')
- bars2 = ax3.bar(x + width/2, val_values_best, width, label='Validation (Best)', alpha=0.8, color='red')
- # Add final model metrics if provided
- if train_metrics_final is not None and val_metrics_final is not None:
- train_values_final = [train_metrics_final[m] for m in metrics_to_plot]
- val_values_final = [val_metrics_final[m] for m in metrics_to_plot]
- bars3 = ax3.bar(x - width/2, train_values_final, width, label='Training (Final)',
- alpha=0.5, color='lightblue', hatch='//')
- bars4 = ax3.bar(x + width/2, val_values_final, width, label='Validation (Final)',
- alpha=0.5, color='lightcoral', hatch='\\\\')
- ax3.set_xlabel('Evaluation Metrics')
- ax3.set_ylabel('Score')
- ax3.set_title('Performance Metrics Comparison')
- ax3.set_xticks(x)
- ax3.set_xticklabels(metrics_to_plot, rotation=45)
- ax3.legend()
- ax3.grid(True, alpha=0.3, axis='y')
- ax3.set_ylim(0, 1) # Set y-axis from 0 to 1
- # Subplot 4: Validation Accuracy Comparison
- ax4 = plt.subplot(2, 3, 4)
- comparison_data = {
- 'Best Model': val_metrics_best['Accuracy'],
- }
- if val_metrics_final is not None:
- comparison_data['Final Epoch Model'] = val_metrics_final['Accuracy']
- models = list(comparison_data.keys())
- acc_values = list(comparison_data.values())
- bars = ax4.bar(models, acc_values, color=['green', 'orange'][:len(models)])
- ax4.set_ylabel('Validation Accuracy')
- ax4.set_title('Validation Accuracy Comparison')
- ax4.set_ylim([0, 1.0])
- # Add value labels on bars
- for i, (bar, val) in enumerate(zip(bars, acc_values)):
- height = bar.get_height()
- ax4.text(bar.get_x() + bar.get_width()/2., height + 0.01,
- f'{val:.4f}', ha='center', va='bottom')
- ax4.grid(True, alpha=0.3, axis='y')
- # Subplot 5: AUC Comparison
- ax5 = plt.subplot(2, 3, 5)
- auc_comparison_data = {
- 'Best Model': val_metrics_best['AUC'],
- }
- if val_metrics_final is not None:
- auc_comparison_data['Final Epoch Model'] = val_metrics_final['AUC']
- models_auc = list(auc_comparison_data.keys())
- auc_values = list(auc_comparison_data.values())
- bars_auc = ax5.bar(models_auc, auc_values, color=['green', 'orange'][:len(models_auc)])
- ax5.set_ylabel('AUC Score')
- ax5.set_title('AUC Score Comparison')
- ax5.set_ylim([0, 1.0])
- # Add value labels on bars
- for i, (bar, val) in enumerate(zip(bars_auc, auc_values)):
- height = bar.get_height()
- ax5.text(bar.get_x() + bar.get_width()/2., height + 0.01,
- f'{val:.4f}', ha='center', va='bottom')
- ax5.grid(True, alpha=0.3, axis='y')
- # Subplot 6: F1-Score Comparison
- ax6 = plt.subplot(2, 3, 6)
- f1_comparison_data = {
- 'Best Model': val_metrics_best['F1-Score'],
- }
- if val_metrics_final is not None:
- f1_comparison_data['Final Epoch Model'] = val_metrics_final['F1-Score']
- models_f1 = list(f1_comparison_data.keys())
- f1_values = list(f1_comparison_data.values())
- bars_f1 = ax6.bar(models_f1, f1_values, color=['green', 'orange'][:len(models_f1)])
- ax6.set_ylabel('F1-Score')
- ax6.set_title('F1-Score Comparison')
- ax6.set_ylim([0, 1.0])
- # Add value labels on bars
- for i, (bar, val) in enumerate(zip(bars_f1, f1_values)):
- height = bar.get_height()
- ax6.text(bar.get_x() + bar.get_width()/2., height + 0.01,
- f'{val:.4f}', ha='center', va='bottom')
- ax6.grid(True, alpha=0.3, axis='y')
- plt.suptitle('Model Performance Comparison: Best vs Final Epoch Model', fontsize=16)
- plt.tight_layout()
- plt.savefig('model_comparison.png', dpi=Config.FIGURE_DPI, bbox_inches='tight')
- plt.show()
- # ==================== Results Saver ====================
- class ResultSaver:
- """Results saver"""
- @staticmethod
- def save_results(best_model, scaler, best_metrics_df, history, best_epoch, best_val_acc,
- final_model=None, final_metrics_df=None, val_df=None,
- y_val=None, y_val_pred_best=None, y_val_pred_final=None):
- """Save all results"""
- print("\n" + "=" * 60)
- print("Saving Model and Results")
- print("=" * 60)
- # Create results directory
- os.makedirs('results_val_acc', exist_ok=True)
- # Get optimal parameters
- optimal_params = Config.OPTIMAL_PARAMS
- # 1. Save best model
- best_model.save('results_val_acc/best_model_val_acc.h5')
- print("✓ Best model saved: results_val_acc/best_model_val_acc.h5")
- # Save final model if provided
- if final_model is not None:
- final_model.save('results_val_acc/final_epoch_model.h5')
- print("✓ Final epoch model saved: results_val_acc/final_epoch_model.h5")
- # 2. Save scaler
- import joblib
- joblib.dump(scaler, 'results_val_acc/scaler.pkl')
- print("✓ Scaler saved: results_val_acc/scaler.pkl")
- # 3. Save validation set predictions for best model
- if val_df is not None and y_val is not None and y_val_pred_best is not None:
- ResultSaver._save_detailed_predictions(val_df, y_val, y_val_pred_best,
- 'best_model', optimal_params)
- # 4. Save validation set predictions for final model (optional)
- if val_df is not None and y_val is not None and y_val_pred_final is not None:
- ResultSaver._save_detailed_predictions(val_df, y_val, y_val_pred_final,
- 'final_epoch_model', optimal_params)
- # 5. Save optimal parameters and training info
- with open('results_val_acc/training_info.txt', 'w') as f:
- f.write("Optimal Model Parameters (based on cross-validation results):\n")
- f.write("=" * 50 + "\n")
- for param, value in optimal_params.items():
- f.write(f"{param}: {value}\n")
- f.write("\nModel Selection Information:\n")
- f.write("=" * 50 + "\n")
- f.write(f"Model Selection Metric: {Config.MODEL_SELECTION_METRIC}\n")
- f.write(f"Best Epoch: {best_epoch + 1}\n")
- f.write(f"Best Validation Accuracy: {best_val_acc:.4f}\n")
- f.write(f"Total Epochs Trained: {len(history.history['loss'])}\n")
- print("✓ Training information saved: results_val_acc/training_info.txt")
- # 6. Save performance metrics
- best_metrics_df.to_csv('results_val_acc/best_model_performance.csv')
- print("✓ Best model performance metrics saved: results_val_acc/best_model_performance.csv")
- if final_metrics_df is not None:
- final_metrics_df.to_csv('results_val_acc/final_epoch_performance.csv')
- print("✓ Final epoch performance metrics saved: results_val_acc/final_epoch_performance.csv")
- # 7. Save training history
- history_df = pd.DataFrame(history.history)
- history_df.to_csv('results_val_acc/training_history.csv')
- print("✓ Training history saved: results_val_acc/training_history.csv")
- # 8. Save configuration summary
- with open('results_val_acc/config_summary.txt', 'w') as f:
- f.write("Model Configuration Summary\n")
- f.write("=" * 60 + "\n\n")
- f.write("Overfitting Prevention Measures:\n")
- f.write(f" - L2 Regularization: {optimal_params['l2_reg']}\n")
- f.write(f" - Dropout Rate: {optimal_params['dropout_rate']}\n")
- f.write(f" - Learning Rate Reduction Factor: {Config.REDUCE_LR_FACTOR}\n")
- f.write(f" - Learning Rate Reduction Patience: {Config.REDUCE_LR_PATIENCE}\n")
- f.write(f" - Early Stopping Patience: {Config.EARLY_STOPPING_PATIENCE}\n\n")
- f.write("Network Architecture:\n")
- f.write(f" - Layer Structure: {optimal_params['layer_sizes']}\n")
- f.write(f" - Total Layers: {len(optimal_params['layer_sizes'])} (hidden) + 1 (output)\n\n")
- f.write("Training Parameters:\n")
- f.write(f" - Learning Rate: {optimal_params['learning_rate']}\n")
- f.write(f" - Batch Size: {optimal_params['batch_size']}\n")
- f.write(f" - Maximum Epochs: {optimal_params['epochs']}\n")
- f.write(f" - Actual Epochs Trained: {len(history.history['loss'])}\n")
- f.write(f" - Model Selection: Based on {Config.MODEL_SELECTION_METRIC}\n")
- f.write(f" - Best Model Epoch: {best_epoch + 1}\n\n")
- f.write("Dataset Information:\n")
- f.write(f" - Training Set Size: 2179\n")
- f.write(f" - Validation Set Size: 545\n")
- f.write(f" - Feature Dimension: 167 (MACCS fingerprints)\n")
- print("✓ Configuration summary saved: results_val_acc/config_summary.txt")
- # 9. Generate final report
- ResultSaver._generate_final_report(best_metrics_df, optimal_params, history,
- best_epoch, best_val_acc, final_metrics_df)
- @staticmethod
- def _save_detailed_predictions(val_df, y_true, y_pred_prob, model_type, optimal_params):
- """Save detailed validation set predictions for a specific model"""
- try:
- # Calculate predicted class
- y_pred_class = (y_pred_prob > 0.5).astype(int)
- # Create detailed predictions DataFrame
- predictions_df = pd.DataFrame({
- 'smiles': val_df['smiles'].values,
- 'true_label': y_true,
- 'predicted_probability': y_pred_prob,
- 'predicted_label': y_pred_class,
- 'prediction_correct': (y_true == y_pred_class).astype(int),
- 'prediction_error': np.abs(y_true - y_pred_prob)
- })
- # Add additional information if available in original dataframe
- for col in val_df.columns:
- if col not in ['smiles', 'label'] and col not in predictions_df.columns:
- predictions_df[col] = val_df[col].values
- # Add prediction confidence level
- predictions_df['confidence_level'] = pd.cut(
- predictions_df['predicted_probability'],
- bins=[0, 0.3, 0.7, 1.0],
- labels=['Low (0-0.3)', 'Medium (0.3-0.7)', 'High (0.7-1.0)'],
- include_lowest=True
- )
- # Calculate prediction performance metrics for each confidence level
- confidence_stats = []
- for level in ['Low (0-0.3)', 'Medium (0.3-0.7)', 'High (0.7-1.0)']:
- subset = predictions_df[predictions_df['confidence_level'] == level]
- if len(subset) > 0:
- accuracy = subset['prediction_correct'].mean()
- confidence_stats.append({
- 'confidence_level': level,
- 'n_molecules': len(subset),
- 'accuracy': accuracy,
- 'avg_probability': subset['predicted_probability'].mean()
- })
- # Sort by prediction probability (descending)
- predictions_df = predictions_df.sort_values('predicted_probability', ascending=False)
- # Save predictions to CSV
- filename = f'results_val_acc/{model_type}_validation_predictions.csv'
- predictions_df.to_csv(filename, index=False)
- # Save confidence level statistics
- if confidence_stats:
- confidence_df = pd.DataFrame(confidence_stats)
- confidence_filename = f'results_val_acc/{model_type}_confidence_statistics.csv'
- confidence_df.to_csv(confidence_filename, index=False)
- print(f"✓ {model_type} confidence statistics saved: {confidence_filename}")
- # Generate summary file
- summary_filename = f'results_val_acc/{model_type}_predictions_summary.txt'
- with open(summary_filename, 'w') as f:
- f.write(f"{'=' * 60}\n")
- f.write(f"{model_type.replace('_', ' ').title()} - Validation Set Predictions Summary\n")
- f.write(f"{'=' * 60}\n\n")
- f.write(f"Model Parameters:\n")
- f.write(f" - Network Architecture: {optimal_params['layer_sizes']}\n")
- f.write(f" - Dropout Rate: {optimal_params['dropout_rate']}\n")
- f.write(f" - Learning Rate: {optimal_params['learning_rate']}\n")
- f.write(f" - L2 Regularization: {optimal_params['l2_reg']}\n\n")
- f.write(f"Overall Performance:\n")
- f.write(f" - Total Molecules: {len(predictions_df)}\n")
- f.write(f" - Correct Predictions: {predictions_df['prediction_correct'].sum()} ({predictions_df['prediction_correct'].mean():.2%})\n")
- f.write(f" - Mean Predicted Probability: {predictions_df['predicted_probability'].mean():.4f}\n")
- f.write(f" - Std Predicted Probability: {predictions_df['predicted_probability'].std():.4f}\n\n")
- f.write(f"Confidence Level Distribution:\n")
- for stats in confidence_stats:
- f.write(f" - {stats['confidence_level']}: {stats['n_molecules']} molecules, Accuracy: {stats['accuracy']:.2%}, Avg Probability: {stats['avg_probability']:.4f}\n")
- f.write(f"\nPrediction Probability Statistics:\n")
- f.write(f" - Min: {predictions_df['predicted_probability'].min():.4f}\n")
- f.write(f" - 25th Percentile: {predictions_df['predicted_probability'].quantile(0.25):.4f}\n")
- f.write(f" - Median: {predictions_df['predicted_probability'].median():.4f}\n")
- f.write(f" - 75th Percentile: {predictions_df['predicted_probability'].quantile(0.75):.4f}\n")
- f.write(f" - Max: {predictions_df['predicted_probability'].max():.4f}\n\n")
- f.write(f"Misclassified Molecules (Top 10 by Error):\n")
- misclassified = predictions_df[predictions_df['prediction_correct'] == 0]
- if len(misclassified) > 0:
- top_misclassified = misclassified.nlargest(10, 'prediction_error')
- for idx, row in top_misclassified.iterrows():
- f.write(f" - SMILES: {row['smiles']}, True: {row['true_label']}, Pred: {row['predicted_label']} (Prob: {row['predicted_probability']:.4f})\n")
- else:
- f.write(f" - No misclassified molecules!\n")
- print(f"✓ {model_type} validation predictions saved: {filename}")
- print(f"✓ {model_type} predictions summary saved: {summary_filename}")
- except Exception as e:
- print(f"⚠ Warning: Failed to save {model_type} predictions: {e}")
- @staticmethod
- def _generate_final_report(best_metrics_df, optimal_params, history,
- best_epoch, best_val_acc, final_metrics_df=None):
- """Generate final report"""
- actual_epochs = len(history.history['loss'])
- report = f"""
- {'=' * 60}
- Developmental Neurotoxicity Prediction DNN Model - Training Complete Report
- {'=' * 60}
- Model Configuration:
- ----------
- Network Architecture: {optimal_params['layer_sizes']}
- Dropout Rate: {optimal_params['dropout_rate']}
- Learning Rate: {optimal_params['learning_rate']}
- L2 Regularization: {optimal_params['l2_reg']}
- Batch Size: {optimal_params['batch_size']}
- Maximum Epochs: {optimal_params['epochs']}
- Actual Epochs Trained: {actual_epochs}
- Model Selection: Based on {Config.MODEL_SELECTION_METRIC}
- Best Model Epoch: {best_epoch + 1}
- Performance Summary (Best Model):
- ----------
- Training Set AUC: {best_metrics_df.loc['AUC', 'Training Set']:.4f}
- Validation Set AUC: {best_metrics_df.loc['AUC', 'Validation Set']:.4f}
- Training Set Accuracy: {best_metrics_df.loc['Accuracy', 'Training Set']:.4f}
- Validation Set Accuracy: {best_metrics_df.loc['Accuracy', 'Validation Set']:.4f}
- (Best Validation Accuracy: {best_val_acc:.4f})
- Training Set F1-Score: {best_metrics_df.loc['F1-Score', 'Training Set']:.4f}
- Validation Set F1-Score: {best_metrics_df.loc['F1-Score', 'Validation Set']:.4f}
- Overfitting Assessment (Best Model):
- -----------------
- AUC Difference (Validation-Training): {best_metrics_df.loc['AUC', 'Difference (Validation-Training)']:.4f}
- F1-Score Difference (Validation-Training): {best_metrics_df.loc['F1-Score', 'Difference (Validation-Training)']:.4f}
- """
- if final_metrics_df is not None:
- report += f"""
- Performance Comparison (Final Epoch vs Best Model):
- -----------------
- Validation Accuracy:
- - Final Epoch: {final_metrics_df.loc['Accuracy', 'Validation Set']:.4f}
- - Best Model: {best_metrics_df.loc['Accuracy', 'Validation Set']:.4f}
- - Improvement: {best_metrics_df.loc['Accuracy', 'Validation Set'] - final_metrics_df.loc['Accuracy', 'Validation Set']:.4f}
- Validation AUC:
- - Final Epoch: {final_metrics_df.loc['AUC', 'Validation Set']:.4f}
- - Best Model: {best_metrics_df.loc['AUC', 'Validation Set']:.4f}
- - Improvement: {best_metrics_df.loc['AUC', 'Validation Set'] - final_metrics_df.loc['AUC', 'Validation Set']:.4f}
- """
- report += f"""
- Evaluation Results:
- ---------
- {'Excellent Validation Performance (AUC > 0.85)' if best_metrics_df.loc['AUC', 'Validation Set'] > 0.85 else 'Moderate Validation Performance'}
- {'No Significant Overfitting (|AUC Difference| < 0.1)' if abs(best_metrics_df.loc['AUC', 'Difference (Validation-Training)']) < 0.1 else 'Potential Overfitting'}
- Prediction Files Generated:
- -----------------
- 1. best_model_validation_predictions.csv - Detailed predictions for each validation molecule
- 2. best_model_confidence_statistics.csv - Performance by confidence level
- 3. best_model_predictions_summary.txt - Summary statistics
- Note: The model was selected based on validation accuracy. The best model (epoch {best_epoch + 1})
- has been saved and will be used for all subsequent analysis.
- All results have been saved to the 'results_val_acc' directory.
- {'=' * 60}
- """
- with open('results_val_acc/final_report.txt', 'w') as f:
- f.write(report)
- print("\n" + report)
- # ==================== Main Program ====================
- def check_dependencies():
- """Check if all required dependencies are installed"""
- required_packages = {
- 'numpy': 'np',
- 'pandas': 'pd',
- 'matplotlib': 'plt',
- 'seaborn': 'sns',
- 'sklearn': 'sklearn',
- 'rdkit': 'Chem',
- 'tensorflow': 'tf'
- }
- missing_packages = []
- for package, import_name in required_packages.items():
- try:
- if package == 'rdkit':
- __import__('rdkit.Chem')
- else:
- __import__(package)
- except ImportError:
- missing_packages.append(package)
- if missing_packages:
- print("Missing required dependencies:")
- for package in missing_packages:
- print(f" - {package}")
- print("\nPlease install using the following commands:")
- print("pip install numpy pandas matplotlib seaborn scikit-learn tensorflow")
- print("conda install -c conda-forge rdkit # or use conda to install rdkit")
- return False
- return True
- def setup_matplotlib():
- """Setup matplotlib style"""
- try:
- # Set seaborn style
- sns.set_style("whitegrid")
- sns.set_palette("husl")
- print("✓ Using seaborn style")
- except Exception as e:
- print(f"⚠ Style setup failed: {e}, using default style")
- def main():
- """Main function: Execute complete model training pipeline"""
- print("=" * 60)
- print("Developmental Neurotoxicity Prediction - DNN QSAR Model")
- print("=" * 60)
- print("Model Selection Strategy: Using validation accuracy to select best model")
- print("Training with Early Stopping: Enabled")
- print("=" * 60)
- # Check dependencies
- if not check_dependencies():
- return
- # Setup matplotlib style
- setup_matplotlib()
- # Initialize components
- config = Config()
- data_processor = DataProcessor()
- model_trainer = ModelTrainer(config)
- model_evaluator = ModelEvaluator()
- result_saver = ResultSaver()
- try:
- # Step 1: Load and preprocess data
- print("\n[Step 1/6] Loading and preprocessing data...")
- train_df, val_df = data_processor.load_data(
- config.TRAIN_FILE, config.VAL_FILE
- )
- X_train, y_train, X_val, y_val, scaler = data_processor.prepare_features(
- train_df, val_df
- )
- # Step 2: Display optimal parameters information
- print("\n[Step 2/6] Configuring model with optimal parameters...")
- optimal_params = config.OPTIMAL_PARAMS
- print(f"\nOptimal Parameter Configuration:")
- print(f" Network Architecture: {optimal_params['layer_sizes']}")
- print(f" Dropout Rate: {optimal_params['dropout_rate']}")
- print(f" Learning Rate: {optimal_params['learning_rate']}")
- print(f" L2 Regularization: {optimal_params['l2_reg']}")
- print(f" Batch Size: {optimal_params['batch_size']}")
- print(f" Maximum Epochs: {optimal_params['epochs']}")
- print(f" Early Stopping Patience: {config.EARLY_STOPPING_PATIENCE}")
- print(f" Model Selection Metric: {config.MODEL_SELECTION_METRIC}")
- # Step 3: Train model with validation accuracy selection and early stopping
- print("\n[Step 3/6] Training model with validation accuracy selection and early stopping...")
- best_model, history, final_model = model_trainer.train_final_model(
- X_train, y_train, X_val, y_val
- )
- # Step 4: Plot training history with best epoch marked and y-axis starting from 0
- print("\n[Step 4/6] Plotting training history (y-axis starts from 0)...")
- model_trainer.plot_training_history(history, optimal_params,
- model_trainer.best_epoch, model_trainer.best_val_accuracy)
- # Step 5: Evaluate both best model and final epoch model
- print("\n[Step 5/6] Evaluating model performance...")
- # Evaluate best model
- print("\n" + "=" * 50)
- print("Evaluating Best Model (Selected by Validation Accuracy)")
- print("=" * 50)
- best_metrics_df, y_train_pred_best, y_val_pred_best, y_val_pred_class_best = model_evaluator.evaluate_model(
- best_model, X_train, y_train, X_val, y_val, "Best Model", val_df
- )
- # Evaluate final epoch model
- print("\n" + "=" * 50)
- print("Evaluating Final Epoch Model")
- print("=" * 50)
- final_metrics_df, y_train_pred_final, y_val_pred_final, y_val_pred_class_final = model_evaluator.evaluate_model(
- final_model, X_train, y_train, X_val, y_val, "Final Epoch Model", val_df
- )
- # Step 6: Plot comparison visualizations
- print("\n[Step 6/6] Plotting performance comparison...")
- model_evaluator.plot_comparison_visualizations(
- y_train, y_train_pred_best, y_train_pred_final,
- y_val, y_val_pred_best, y_val_pred_final,
- y_val, y_val_pred_class_best,
- best_metrics_df.loc[:, 'Training Set'].to_dict(),
- best_metrics_df.loc[:, 'Validation Set'].to_dict(),
- final_metrics_df.loc[:, 'Training Set'].to_dict(),
- final_metrics_df.loc[:, 'Validation Set'].to_dict()
- )
- # Save all results
- result_saver.save_results(
- best_model, scaler, best_metrics_df, history,
- model_trainer.best_epoch, model_trainer.best_val_accuracy,
- final_model, final_metrics_df, val_df, y_val, y_val_pred_best, y_val_pred_final
- )
- print("\n" + "=" * 60)
- print("🎉 Model training and evaluation complete!")
- print("=" * 60)
- print(f"\nSummary:")
- print(f" - Best model saved from epoch: {model_trainer.best_epoch + 1}")
- print(f" - Best validation accuracy: {model_trainer.best_val_accuracy:.4f}")
- print(f" - Final epoch validation accuracy: {final_metrics_df.loc['Accuracy', 'Validation Set']:.4f}")
- print(f" - Improvement by model selection: {model_trainer.best_val_accuracy - final_metrics_df.loc['Accuracy', 'Validation Set']:.4f}")
- if model_trainer.early_stopped_epoch:
- print(f" - Early stopping triggered at epoch: {model_trainer.early_stopped_epoch}")
- print(f"\nPrediction Files Generated:")
- print(f" - results_val_acc/best_model_validation_predictions.csv")
- print(f" - results_val_acc/best_model_confidence_statistics.csv")
- print(f" - results_val_acc/best_model_predictions_summary.txt")
- print("=" * 60)
- except FileNotFoundError as e:
- print(f"\n❌ Error: File not found - {e}")
- print("Please ensure the following files exist in the current directory:")
- print(f" - {config.TRAIN_FILE}")
- print(f" - {config.VAL_FILE}")
- except Exception as e:
- print(f"\n❌ Error: {e}")
- import traceback
- traceback.print_exc()
- if __name__ == "__main__":
- # Check required files
- if not os.path.exists(Config.TRAIN_FILE):
- print(f"Error: Training file {Config.TRAIN_FILE} does not exist")
- print(f"Please ensure the following files exist in the current directory:")
- print(f"1. {Config.TRAIN_FILE}")
- print(f"2. {Config.VAL_FILE}")
- elif not os.path.exists(Config.VAL_FILE):
- print(f"Error: Validation file {Config.VAL_FILE} does not exist")
- else:
- # Run main program
- main()
DNN-test_selection.py at commit 514073b, no license · at the source
Overview
- Shandong Key Laboratory of Digital Diagnosis and Treatment of Thoracic Oncology, Shandong Engineering Research Center of Precision Diagnosis and Treatment Technology for Neuro-Oncology, Department of Clinical Pharmacy, The First Affiliated Hospital of Shandong First Medical University, Shandong Provincial Qianfoshan Hospital Jinan 250014 China
- Department of Pharmacy, Beijing Hospital, National Center of Gerontology, Institute of Geriatric Medicine, Chinese Academy of Medical Sciences Beijing 100730 China
- State Key Laboratory of Neurology and Oncology Drug Development, The First Affiliated Hospital of Shandong First Medical University, Shandong Provincial Qianfoshan Hospital Jinan 250014 China
Abstract
Developmental neurotoxicity (DNT) represents a critical yet underevaluated toxicity endpoint within current chemical safety assessment frameworks, particularly in the context of drug development. In the present study, we constructed a standardized benchmark dataset comprising 2724 structurally curated compounds, including 1362 DNT-positive chemicals derived from the Developmental Neurotox Reference List (DNTREF) of the U.S. Environmental Protection Agency (EPA) and 1362 property-matched presumed negative compounds (non-neuroactive drug-like compounds) selected from the ChEMBL database. Using this benchmark dataset, we systematically evaluated seven representative quantitative structure activity relationship (QSAR) modeling paradigms, covering traditional machine learning, deep neural networks, molecular language models, and graph neural networks. Across all models, the area under the receiver operating characteristic curve (AUC) values ranged from 0.85 to 0.90 on an independent test set. Among these, a deep neural network based on MACCS fingerprints achieved the highest sensitivity in identifying DNT-positive compounds, supporting its utility in early-stage drug safety screening where minimizing false negatives is critical. Furthermore, SHAP-based interpretability analysis revealed key structural motifs associated with DNT, highlighting the roles of hydrophobic aromatic frameworks, electrophilic reactivity, and polar functional groups in modulating predicted neurodevelopmental risk. Collectively, this study provides a reproducible benchmark dataset and an interpretable QSAR framework that supports early DNT risk prioritization and offers actionable structural insights for medicinal chemistry optimization in drug discovery.
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 13 matches between paragraphs and lines of code.
lixiao1688/DNT_Benchmark_Dataset_Model
514073b77308152898322ed1e5380f12c0f7b426, 27 January 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
9 files
- 1-Benchmark_Dataset_Prep
aration/ , Python, 136 lines, 1 matchDNTREF_curation.py - 1-Benchmark_Dataset_Prep
aration/ , Python, 105 lines, 1 matchhard_negative_matching.p y - 1-Benchmark_Dataset_Prep
aration/ , Python, 59 lines, 1 matchplot_umap_chemical_space .py - 3-DNN_MACCS_modeling/
DNN-independent validation.py , Python, 778 lines, 1 match - 3-DNN_MACCS_modeling/
DNN-test_selection.py , Python, 1,147 lines, 5 matches - 3-DNN_MACCS_modeling/
DNN-training-5-fold-CV.p , Python, 628 lines, 1 matchy - 4-applicability_domain_S
HAP_analysis/ , Python, 462 lines, 2 matchesApplication_Domain_UMAP_ tSNE.py - 4-applicability_domain_S
HAP_analysis/ , Python, 1,220 lines, 1 matchSHAP-analysisl.py - README.md, Text, 46 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 8 scripts, each with its path and the digest of its content;
- 13 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data and model availability
The developmental neurotoxicity benchmark dataset constructed in this study includes standardized SMILES representations of all compounds, corresponding binary toxicity labels, and the fixed training, validation, and independent test set partitions used for model development and evaluation. These data are deposited in a public repository to ensure full reproducibility of the reported results. The dataset is expected to serve as a valuable resource for the computational toxicology and drug discovery communities, enabling further methodological development and benchmarking.
In addition, the scripts for MACCS fingerprint generation, the trained weights of the optimal DNN_MACCS model, and all scripts used for model training, inference, and SHAP-based interpretability analysis will be made publicly available.
All scripts for AD analysis and chemical space visualization using UMAP and t-SNE will also be released to support transparent assessment of model scope and result interpretation.
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Data availability
The data utilized in this study were derived from the DNTREF from U.S. EPA, and ChEMBL database. Complete data (including processed datasets, and all scripts necessary to reproduce the study) is available on GitHub at https://
Supplementary information (SI): detailed data processing for benchmark dataset; detailed network architectures, hyperparameter settings, and training procedures; a summary of predictive performance for all models on validation set; unified chemical standardization process for data collection, structure preprocessing, and sample selection; training history and model comparison of the DNN_MACCS model. See DOI: https://
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 4 funders, 46 references.
Cite
This paper
Ma, H., Zhang, W., Liu, F., Ni, R., Wang, X., Sun, Y., Sun, X., & Li, X. (2026). A benchmark dataset and interpretable deep learning framework for drug-induced developmental neurotoxicity prediction. RSC advances, 16(36), 37450-37464. https://
BibTeX
@article{ma2026benchmark
author = {Ma, Hongting and Zhang, Wenhui and Liu, Fengxi and Ni, Rong and Wang, Xue and Sun, Yanying and Sun, Xuelin and Li, Xiao},
title = {{A benchmark dataset and interpretable deep learning framework for drug-induced developmental neurotoxicity prediction}},
journal = {RSC advances},
year = {2026},
month = jul,
volume = {16},
number = {36},
pages = {37450--37464},
publisher = {Royal Society of Chemistry},
issn = {2046-2069},
doi = {10.1039/
url = {https://
pmid = {42440932},
pmcid = {PMC13334446}
}
RIS
TY - JOUR
AU - Ma, Hongting
AU - Zhang, Wenhui
AU - Liu, Fengxi
AU - Ni, Rong
AU - Wang, Xue
AU - Sun, Yanying
AU - Sun, Xuelin
AU - Li, Xiao
TI - A benchmark dataset and interpretable deep learning framework for drug-induced developmental neurotoxicity prediction
T2 - RSC advances
J2 - RSC Adv
PY - 2026
DA - 2026/
VL - 16
IS - 36
SP - 37450
EP - 37464
SN - 2046-2069
PB - Royal Society of Chemistry
DO - 10.1039/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1039/
"type": "article-journal",
"title": "A benchmark dataset and interpretable deep learning framework for drug-induced developmental neurotoxicity prediction",
"container-title": "RSC advances",
"author": [
{
"family": "Ma",
"given": "Hongting"
},
{
"family": "Zhang",
"given": "Wenhui"
},
{
"family": "Liu",
"given": "Fengxi"
},
{
"family": "Ni",
"given": "Rong"
},
{
"family": "Wang",
"given": "Xue"
},
{
"family": "Sun",
"given": "Yanying"
},
{
"family": "Sun",
"given": "Xuelin"
},
{
"family": "Li",
"given": "Xiao"
}
],
"container-title-short":
"volume": "16",
"issue": "36",
"page": "37450-37464",
"DOI": "10.1039/
"PMID": "42440932",
"PMCID": "PMC13334446",
"ISSN": "2046-2069",
"publisher": "Royal Society of Chemistry",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
6
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: RDKit, SHAP, Keras, 8 other tools
- [2] doi:10.1093/nar/gkag706 [code]
- scDifformer: diffusion-based post-training for virtual cell modeling across large-scale single-cell data.Journal: Nucleic acids researchIn common: RDKit, Keras, UMAP, 7 other tools, 1 reference
- [3] doi:10.1038/s41598-026-48613-0 [code]
- An snRNA-seq aging clock for the fruit fly head sheds light on sex-biased aging.Journal: Scientific reportsIn common: SHAP, Keras, TensorFlow, 6 other tools, 2 references
- [4] doi:10.1126/sciadv.aed3650 [code]
- Truthful visualizations for mass spectrometry imaging enable high-spatial-resolution interactive &
lt;i& gt;m/ z& lt;/ i& gt; mapping and exploration. Journal: Science advancesIn common: SHAP, Keras, UMAP, 6 other tools, methods / tools - [5] doi:10.1177/13872877261453512 [code]
- Fluorescence spectroscopy and machine learning methods for detection of Alzheimer's disease from circulating white blood cells.Journal: Journal of Alzheimer's disease : JADIn common: Keras, UMAP, TensorFlow, 6 other tools, methods / tools
- [6] doi:10.1038/s41592-026-03057-2 [code]
- CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species.Journal: Nature methodsIn common: Keras, UMAP, TensorFlow, 6 other tools, methods / tools
- [7] doi:10.3390/s26175327 [code]
- Subject Identity Confounds qEEG Emotion Recognition on DEAP and DREAMER.Journal: Sensors (Basel, Switzerland)In common: SHAP, Keras, TensorFlow, 6 other tools
- [8] doi:10.1371/journal.pcbi.1014615 [code]
- Toward reliable machine learning models for neural circuit inference: A diagnostic study of CNNs on spike trains.Journal: PLoS computational biologyIn common: SHAP, Keras, TensorFlow, 6 other tools
- [9] doi:10.1038/s41467-026-75700-7 [code]
- Gene regulatory innovations from transposable elements in primate cerebellum development.Journal: Nature communicationsIn common: SHAP, Keras, TensorFlow, 6 other tools
- [10] doi:10.1038/s41598-026-47047-y [code]
- Imaging-genetics-based dementia risk prediction using deep survival neural networks in the Rotterdam Study.Journal: Scientific reportsIn common: SHAP, Keras, TensorFlow, 6 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 8 scripts, and 13 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:5e9110b98771798d…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
