Biofuel-Optimiser / core /evolution /train_one_class_svm.py
carrotcake3's picture
Upload 49 files
6f5a0e0 verified
Raw
History Blame Contribute Delete
9.47 kB
"""
SIMPLE Step-by-Step Mixture Embedding Extractor
For training One-Class SVM on your mixture training data.
"""
import numpy as np
import torch
from typing import List
import pandas as pd
def extract_mixture_embeddings_simple(
csv_path: str = "data/database/mixture_training_dataset.csv",
model_dir: str = "models/mixture/dcn"
):
"""
Extract mixture embeddings from your training data in 5 simple steps.
Args:
csv_path: Path to mixture_training_dataset.csv
model_dir: Path to trained models directory
Returns:
X_train: (n_mixtures, embedding_dim) numpy array for your SVM
"""
print("="*70)
print("STEP-BY-STEP MIXTURE EMBEDDING EXTRACTION")
print("="*70)
# =========================================================================
# STEP 1: Load your mixture DCN predictor
# =========================================================================
print("\nStep 1: Loading DCN predictor...")
from core.predictors.mixture.mixture_dcn_predictor import MixtureDCNPredictor
predictor = MixtureDCNPredictor(model_dir=model_dir)
predictor._initialize_models()
print(f"✓ Loaded {len(predictor.models)} models")
# =========================================================================
# STEP 2: Load your training data from CSV
# =========================================================================
print(f"\nStep 2: Loading training data from CSV...")
# Try different encodings
df = None
for encoding in ['utf-8', 'latin-1', 'iso-8859-1', 'cp1252']:
try:
df = pd.read_csv(csv_path, encoding=encoding)
print(f"✓ Read CSV with {encoding} encoding")
break
except:
continue
if df is None:
raise ValueError("Could not read CSV with any encoding")
print(f"✓ Loaded {len(df)} rows from CSV")
# =========================================================================
# STEP 3: Convert CSV rows to mixture format
# =========================================================================
print("\nStep 3: Converting CSV to mixture format...")
mixtures = []
for idx, row in df.iterrows():
# Extract InChI strings from fuel1 inchi, fuel2 inchi, etc.
inchis = []
for i in range(1, 12): # Check up to fuel11
col = f'fuel{i} inchi'
if col in df.columns and pd.notna(row.get(col)):
inchi = str(row[col]).strip()
# Clean up InChI (remove quotes if present)
inchi = inchi.strip('"').strip("'")
if inchi and inchi != 'nan' and inchi != '':
inchis.append(inchi)
if len(inchis) == 0:
continue
# Extract mole fractions from "molar fraction fuel 1", etc.
fractions = []
for i in range(1, len(inchis)): # N-1 fractions
col = f'molar fraction fuel {i}'
if col in df.columns and pd.notna(row.get(col)):
frac = float(row[col])
fractions.append(frac)
else:
# If fraction column doesn't exist, skip this mixture
break
# Only add if we have the correct number of fractions (N-1 for N components)
if len(fractions) == len(inchis) - 1:
mixtures.append({
'inchis': inchis,
'fractions': fractions
})
elif len(inchis) == 2 and len(fractions) == 1:
# Special case: binary mixture with 1 fraction
mixtures.append({
'inchis': inchis,
'fractions': fractions
})
print(f"✓ Converted {len(mixtures)} valid mixtures")
print(f" Example mixture: {len(mixtures[0]['inchis'])} components")
# =========================================================================
# STEP 4: Extract embeddings using a hook
# =========================================================================
print("\nStep 4: Extracting mixture embeddings...")
print(" (This is where we hook into the model)")
all_embeddings = []
# We'll extract from just the first model for simplicity
# (You can average across all 10 later if you want)
model = predictor.models[0]
model.eval()
# Hook to capture the mixture embedding
captured_embeddings = []
def hook_fn(module, input, output):
"""This captures the mixture embedding AFTER pooling."""
# The input to FFN is the mixture embedding we want!
captured_embeddings.append(input[0].detach().cpu())
# Register hook on the FFN (it receives the mixture embedding as input)
hook = model.ffn.register_forward_hook(hook_fn)
# Process in batches
batch_size = 50
from core.predictors.mixture.solvation_predictor.data.data import (
DataPoint, DatapointList, MolencoderDatabase, DataTensor
)
for batch_start in range(0, len(mixtures), batch_size):
batch_end = min(batch_start + batch_size, len(mixtures))
batch_mixtures = mixtures[batch_start:batch_end]
# Create datapoints
datapoints = []
mol_encoder_db = MolencoderDatabase()
for mix in batch_mixtures:
try:
dp = DataPoint(
smiles=mix['inchis'],
targets=[0.0],
features=[],
molefracs=mix['fractions'],
inp=predictor.args,
mol_encoders=mol_encoder_db
)
datapoints.append(dp)
except Exception as e:
# Skip invalid mixtures
continue
if len(datapoints) == 0:
continue
data = DatapointList(datapoints)
# Create tensors (same as in evaluate.py)
mol_encodings = []
tensors = []
for molecule in range(predictor.args.max_num_mols):
mol_encodings.append([])
for mol in mol_encodings:
for d in datapoints:
encoders = d.get_mol_encoder()
if len(encoders) < predictor.args.max_num_mols:
for j in range(predictor.args.max_num_mols - len(encoders)):
encoders.append(encoders[0])
mol.append(encoders[mol_encodings.index(mol)])
tensors.append(DataTensor(mol, predictor.args, property=predictor.args.property))
# Forward pass (hook will capture the mixture embedding)
with torch.no_grad():
_ = model(data, tensors)
if (batch_start // batch_size + 1) % 5 == 0:
print(f" Processed {batch_end}/{len(mixtures)} mixtures...")
# Remove hook
hook.remove()
# Stack all embeddings
X_train = torch.cat(captured_embeddings, dim=0).numpy()
print(f"✓ Extracted {X_train.shape[0]} embeddings")
print(f" Embedding dimension: {X_train.shape[1]}")
# =========================================================================
# STEP 5: Return X_train for your SVM
# =========================================================================
print("\nStep 5: Ready for SVM training!")
print(f" X_train shape: {X_train.shape}")
print("="*70)
return X_train
# =============================================================================
# USAGE EXAMPLE
# =============================================================================
if __name__ == "__main__":
# Extract embeddings
X_train = extract_mixture_embeddings_simple()
print("\n" + "="*70)
print("TRAINING ONE-CLASS SVM")
print("="*70)
# Your code from above!
from sklearn.svm import OneClassSVM
from sklearn.preprocessing import StandardScaler
import numpy as np
# STEP 1: Normalize the embeddings (IMPORTANT!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# STEP 2: Train SVM with better parameters
svm = OneClassSVM(
kernel='rbf',
gamma='scale', # Auto-calculate based on data (much better!)
nu=0.02 # 2% outliers (from the paper)
)
svm.fit(X_train_scaled)
# STEP 3: Check the scores
scores = svm.decision_function(X_train_scaled)
print(f"\nDecision function scores:")
print(f" Min: {scores.min():.3f}")
print(f" Max: {scores.max():.3f}")
print(f" Mean: {scores.mean():.3f}")
print(f" Std: {scores.std():.3f}")
# STEP 4: Check predictions
predictions = svm.predict(X_train_scaled)
n_outliers = (predictions == -1).sum()
print(f"\nOutliers in training set: {n_outliers}/{len(X_train_scaled)} ({n_outliers/len(X_train_scaled)*100:.1f}%)")
# STEP 5: Save BOTH scaler and SVM
import pickle
with open('models/mixture/mixture_ad_svm.pkl', 'wb') as f:
pickle.dump({
'svm': svm,
'scaler': scaler,
'nu': 0.02,
'embedding_dim': X_train.shape[1]
}, f)
print("\n✓ Saved SVM + Scaler to models/mixture/mixture_ad_svm.pkl")