EpiADR-Net / frontend /src /components /TrainingPanel.tsx
ADjayantan
EpiADR-Net: Integrated React + TypeScript enterprise web UI application in frontend/ directory
654bfe6
Raw
History Blame Contribute Delete
12.9 kB
import React, { useState } from 'react';
import { EpiADRHyperparameters, EpochTrainingMetric, ModelTrainingSummary } from '../types';
import { globalEpiADREngine } from '../utils/epiAdrEngine';
import { SIDER_BENCHMARK_DRUGS } from '../utils/siderDataset';
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid, Legend } from 'recharts';
import { Cpu, Play, Square, RotateCcw, Activity, Layers, CheckCircle, BarChart2, ShieldAlert } from 'lucide-react';
interface TrainingPanelProps {
useTissueConditioning: boolean;
setUseTissueConditioning: (val: boolean) => void;
}
export const TrainingPanel: React.FC<TrainingPanelProps> = ({
useTissueConditioning,
setUseTissueConditioning
}) => {
const [hyperparams, setHyperparams] = useState<EpiADRHyperparameters>({
useTissueConditioning,
crossAttentionHeads: 16,
learningRate: 0.001,
epochs: 40,
batchSize: 16,
optimizer: 'adam',
posWeight: 2.5,
regularizationL2: 0.001,
dropoutRate: 0.2,
mcDropoutPasses: 30
});
const [isTraining, setIsTraining] = useState<boolean>(false);
const [epochHistory, setEpochHistory] = useState<EpochTrainingMetric[]>([]);
const [trainingSummary, setTrainingSummary] = useState<ModelTrainingSummary | null>(null);
const handleStartTraining = async () => {
setIsTraining(true);
setEpochHistory([]);
setTrainingSummary(null);
try {
const summary = await globalEpiADREngine.trainEpiADRModel(
SIDER_BENCHMARK_DRUGS,
{ ...hyperparams, useTissueConditioning },
(metric) => {
setEpochHistory(prev => [...prev, metric]);
}
);
setTrainingSummary(summary);
} catch (err: any) {
console.error("Training error:", err);
} finally {
setIsTraining(false);
}
};
const handleStopTraining = () => {
globalEpiADREngine.cancelTraining();
setIsTraining(false);
};
return (
<div className="space-y-6">
{/* Training Configuration Grid */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Left Column: Hyperparameter Controls */}
<div className="lg:col-span-4 bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-lg space-y-4">
<div className="flex items-center space-x-2 border-b border-slate-800 pb-3">
<Cpu className="w-5 h-5 text-indigo-400" />
<h2 className="text-base font-bold text-white">EpiADR-Net Model Architecture</h2>
</div>
{/* Mode Switcher */}
<div className="bg-slate-950 p-3 rounded-xl border border-slate-800 space-y-2">
<span className="text-xs font-semibold text-slate-300 block">Scientific Controlled Baseline</span>
<div className="flex space-x-2">
<button
onClick={() => {
setUseTissueConditioning(true);
setHyperparams({ ...hyperparams, useTissueConditioning: true });
}}
className={`flex-1 py-1.5 px-2 rounded-lg text-xs font-bold transition-all border ${
useTissueConditioning
? 'bg-indigo-600 text-white border-indigo-400 shadow-md'
: 'bg-slate-900 text-slate-400 border-slate-800'
}`}
>
Tissue-Conditioned
</button>
<button
onClick={() => {
setUseTissueConditioning(false);
setHyperparams({ ...hyperparams, useTissueConditioning: false });
}}
className={`flex-1 py-1.5 px-2 rounded-lg text-xs font-bold transition-all border ${
!useTissueConditioning
? 'bg-amber-600 text-white border-amber-400 shadow-md'
: 'bg-slate-900 text-slate-400 border-slate-800'
}`}
>
Molecule-Only
</button>
</div>
<p className="text-[11px] text-slate-400">
{useTissueConditioning
? 'Fuses SMILES 256-bit fingerprint with GTEx V8 128-dim organ transcriptomic vectors via 16-Head Cross-Attention.'
: 'Disables transcriptomic profiles to measure scientific accuracy uplift of human gene expression data.'}
</p>
</div>
{/* Hyperparameters */}
<div className="space-y-3 pt-1 text-xs">
<div className="space-y-1">
<div className="flex justify-between text-slate-300 font-semibold">
<span>Epochs</span>
<span className="font-mono text-indigo-300">{hyperparams.epochs}</span>
</div>
<input
type="range"
min="10"
max="100"
step="5"
disabled={isTraining}
value={hyperparams.epochs}
onChange={(e) => setHyperparams({ ...hyperparams, epochs: parseInt(e.target.value) })}
className="w-full accent-indigo-500 cursor-pointer h-1.5 bg-slate-800 rounded-lg"
/>
</div>
<div className="space-y-1">
<div className="flex justify-between text-slate-300 font-semibold">
<span>Learning Rate (η)</span>
<span className="font-mono text-indigo-300">{hyperparams.learningRate}</span>
</div>
<select
value={hyperparams.learningRate}
disabled={isTraining}
onChange={(e) => setHyperparams({ ...hyperparams, learningRate: parseFloat(e.target.value) })}
className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-200"
>
<option value="0.005">0.005 (Fast)</option>
<option value="0.001">0.001 (Recommended)</option>
<option value="0.0003">0.0003 (Fine)</option>
</select>
</div>
<div className="space-y-1">
<div className="flex justify-between text-slate-300 font-semibold">
<span>Class Imbalance Weight (pos_weight)</span>
<span className="font-mono text-indigo-300">{hyperparams.posWeight}x</span>
</div>
<input
type="range"
min="1.0"
max="5.0"
step="0.5"
disabled={isTraining}
value={hyperparams.posWeight}
onChange={(e) => setHyperparams({ ...hyperparams, posWeight: parseFloat(e.target.value) })}
className="w-full accent-indigo-500 cursor-pointer h-1.5 bg-slate-800 rounded-lg"
/>
</div>
<div className="space-y-1">
<div className="flex justify-between text-slate-300 font-semibold">
<span>L2 Weight Regularization</span>
<span className="font-mono text-indigo-300">{hyperparams.regularizationL2}</span>
</div>
<input
type="range"
min="0"
max="0.01"
step="0.001"
disabled={isTraining}
value={hyperparams.regularizationL2}
onChange={(e) => setHyperparams({ ...hyperparams, regularizationL2: parseFloat(e.target.value) })}
className="w-full accent-indigo-500 cursor-pointer h-1.5 bg-slate-800 rounded-lg"
/>
</div>
</div>
{/* Controls */}
<div className="pt-3 border-t border-slate-800 flex space-x-2">
{!isTraining ? (
<button
onClick={handleStartTraining}
className="flex-1 py-3 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl font-bold text-xs shadow-lg flex items-center justify-center space-x-2 transition-all active:scale-95"
>
<Play className="w-4 h-4 fill-white" />
<span>Train EpiADR-Net</span>
</button>
) : (
<button
onClick={handleStopTraining}
className="flex-1 py-3 bg-rose-600 hover:bg-rose-500 text-white rounded-xl font-bold text-xs shadow-lg flex items-center justify-center space-x-2 transition-all"
>
<Square className="w-4 h-4 fill-white" />
<span>Halt Training</span>
</button>
)}
</div>
</div>
{/* Right Column: Live Epoch Loss & AUROC Charts */}
<div className="lg:col-span-8 bg-slate-900 border border-slate-800 rounded-2xl p-5 shadow-lg space-y-4">
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
<div className="flex items-center space-x-2">
<Activity className="w-5 h-5 text-indigo-400" />
<h3 className="text-base font-bold text-white">Live Training & Validation Dynamics</h3>
</div>
{isTraining && (
<span className="flex items-center space-x-1 text-xs text-indigo-400 font-mono animate-pulse">
<span className="w-2 h-2 rounded-full bg-indigo-400"></span>
<span>Optimizing Epoch {epochHistory.length}/{hyperparams.epochs}...</span>
</span>
)}
</div>
{/* Loss Curve */}
<div className="h-52 bg-slate-950 p-2 rounded-xl border border-slate-800">
<span className="text-[11px] text-slate-400 font-mono font-semibold px-2 block">Binary Crossentropy Loss</span>
<ResponsiveContainer width="100%" height="85%">
<LineChart data={epochHistory}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="epoch" stroke="#64748b" fontSize={10} />
<YAxis stroke="#64748b" fontSize={10} domain={[0, 1]} />
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', borderRadius: '8px', fontSize: '11px' }} />
<Legend wrapperStyle={{ fontSize: '11px', paddingTop: '4px' }} />
<Line type="monotone" dataKey="trainLoss" stroke="#6366f1" strokeWidth={2} dot={false} name="Train Loss" />
<Line type="monotone" dataKey="valLoss" stroke="#f43f5e" strokeWidth={2} dot={false} name="Val Loss" />
</LineChart>
</ResponsiveContainer>
</div>
{/* AUROC Curve */}
<div className="h-52 bg-slate-950 p-2 rounded-xl border border-slate-800">
<span className="text-[11px] text-slate-400 font-mono font-semibold px-2 block">Validation AUROC Trajectory</span>
<ResponsiveContainer width="100%" height="85%">
<LineChart data={epochHistory}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="epoch" stroke="#64748b" fontSize={10} />
<YAxis stroke="#64748b" fontSize={10} domain={[0.5, 1.0]} />
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', borderRadius: '8px', fontSize: '11px' }} />
<Legend wrapperStyle={{ fontSize: '11px', paddingTop: '4px' }} />
<Line type="monotone" dataKey="valAUROC" stroke="#10b981" strokeWidth={2.5} dot={false} name="Validation AUROC" />
</LineChart>
</ResponsiveContainer>
</div>
{/* Model Training Summary Metrics */}
{trainingSummary && (
<div className="bg-slate-950 p-4 rounded-xl border border-indigo-500/30 grid grid-cols-2 sm:grid-cols-4 gap-3 text-center text-xs font-mono animate-fade-in">
<div>
<span className="text-slate-400 block text-[10px]">Validation AUROC</span>
<span className="text-emerald-400 font-bold text-base">{trainingSummary.finalValAUROC.toFixed(3)}</span>
</div>
<div>
<span className="text-slate-400 block text-[10px]">Macro F1 Score</span>
<span className="text-indigo-400 font-bold text-base">{trainingSummary.finalF1Score.toFixed(3)}</span>
</div>
<div>
<span className="text-slate-400 block text-[10px]">Final Loss</span>
<span className="text-rose-400 font-bold text-base">{trainingSummary.finalValLoss.toFixed(4)}</span>
</div>
<div>
<span className="text-slate-400 block text-[10px]">Training Duration</span>
<span className="text-slate-200 font-bold text-base">{trainingSummary.trainingTimeMs} ms</span>
</div>
</div>
)}
</div>
</div>
</div>
);
};