Download scripts/train.py from OneScience-Group/RONOM: direct link, hf CLI and curl.
- Browser
- Download file 9.06 kB
-
https://huggingface.co/OneScience-Group/RONOM/resolve/main/scripts/train.py
- Command line
-
hf download hf://OneScience-Group/RONOM/scripts/train.py
-
curl -L -o train.py https://huggingface.co/OneScience-Group/RONOM/resolve/main/scripts/train.py
9.06 kB
| """Full 5000-epoch Burgers input-generalization experiment; validation selects best.""" | |
| import csv | |
| import os | |
| from pathlib import Path | |
| import time | |
| import torch | |
| from torch.utils.data import DataLoader, TensorDataset | |
| from common import (parser, load_config, seed_everything, device_for, load_splits, | |
| resolve, save_json, digest, build_model, checkpoint_save, evaluate, | |
| update_run_manifest) | |
| def main(): | |
| p = parser(__doc__) | |
| p.add_argument('--resume', default=None, help='Resume a locally generated last checkpoint') | |
| args = p.parse_args() | |
| cfg = load_config(args.config) | |
| tr = cfg['training'] | |
| if tr['epochs'] != 5000 or tr['batch_size'] != 32: | |
| raise ValueError('Formal experiment requires 5000 epochs and batch size 32') | |
| seed_everything(cfg['seed'], tr['num_threads']) | |
| device = device_for(tr['device']) | |
| result_dir = resolve(cfg['paths']['results']) | |
| result_dir.mkdir(parents=True, exist_ok=True) | |
| history_path = result_dir / 'training_history.csv' | |
| if history_path.exists() and not args.resume: | |
| raise FileExistsError('Training history already exists. Use --resume to continue this run.') | |
| splits, times, data_path = load_splits(cfg, ['train', 'validation']) | |
| train_u, train_y, train_ids = splits['train'] | |
| val_u, val_y, val_ids = splits['validation'] | |
| train_u, train_y = train_u.to(device), train_y.to(device) | |
| val_u, val_y = val_u.to(device), val_y.to(device) | |
| loader = DataLoader(TensorDataset(train_u, train_y), batch_size=32, | |
| shuffle=True, drop_last=False, num_workers=0) | |
| model = build_model(cfg, device) | |
| optimizer = torch.optim.Adam(model.parameters(), lr=tr['learning_rate'], | |
| weight_decay=tr['weight_decay']) | |
| scheduler = torch.optim.lr_scheduler.MultiplicativeLR( | |
| optimizer, lr_lambda=lambda _: tr['scheduler_gamma']) | |
| start_epoch, best_mse, best_epoch = 0, float('inf'), None | |
| if args.resume: | |
| old = torch.load(resolve(args.resume), map_location='cpu', weights_only=False) | |
| if old['model_config'] != cfg['model'] or old['data_sha256'] != digest(data_path): | |
| raise ValueError('Resume checkpoint architecture or source data differs') | |
| model.load_state_dict(old['model_state_dict']) | |
| optimizer.load_state_dict(old['optimizer_state_dict']) | |
| scheduler.load_state_dict(old['scheduler_state_dict']) | |
| torch.set_rng_state(old['torch_rng']) | |
| torch.cuda.set_rng_state_all(old['cuda_rng']) | |
| start_epoch, best_mse, best_epoch = old['epoch'], old['best_validation_mse'], old['best_epoch'] | |
| # Logs may contain epochs beyond the last atomic checkpoint after an | |
| # interrupted allocation. Replay those epochs from saved RNG/optimizer | |
| # state, keeping exactly one history row for every completed epoch. | |
| with history_path.open(newline='') as previous: | |
| reader = csv.DictReader(previous) | |
| fields = reader.fieldnames | |
| retained = [r for r in reader if int(r['epoch']) <= start_epoch] | |
| if [int(r['epoch']) for r in retained] != list(range(1, start_epoch + 1)): | |
| raise ValueError('Resume history is incomplete before the checkpoint epoch') | |
| temporary_history = history_path.with_suffix('.csv.tmp') | |
| with temporary_history.open('w', newline='') as previous: | |
| writer = csv.DictWriter(previous, fieldnames=fields) | |
| writer.writeheader() | |
| writer.writerows(retained) | |
| temporary_history.replace(history_path) | |
| print(f'Resuming after completed epoch {start_epoch}', flush=True) | |
| data_sha = digest(data_path) | |
| save_json(result_dir / 'run_manifest.json', { | |
| 'status': 'running', 'config': cfg, 'data_sha256': data_sha, | |
| 'slurm_job_id': os.environ.get('SLURM_JOB_ID'), 'torch_version': torch.__version__, | |
| 'train_indices': train_ids.tolist(), 'validation_indices': val_ids.tolist(), | |
| 'test_indices_reserved': [800, 1000], 'times': times.tolist(), | |
| 'model_parameters': sum(v.numel() for v in model.parameters()), | |
| 'test_used_for_selection': False, 'precision': 'float32'}) | |
| header = ['epoch', 'train_half_mse', 'train_mse', 'last_batch_half_mse', 'learning_rate', | |
| 'validation_mse', 'validation_rmse_mean', 'validation_rmse_std', 'elapsed_seconds'] | |
| started = time.monotonic() | |
| with history_path.open('a' if args.resume else 'w', newline='', buffering=1) as handle: | |
| writer = csv.DictWriter(handle, fieldnames=header) | |
| if not args.resume: | |
| writer.writeheader() | |
| for epoch_index in range(start_epoch, tr['epochs']): | |
| epoch = epoch_index + 1 | |
| model.train() | |
| total, seen, last_loss = 0., 0, None | |
| lr_used = optimizer.param_groups[0]['lr'] | |
| for u, target in loader: | |
| optimizer.zero_grad(set_to_none=True) | |
| predicted = model(u, times) | |
| loss = 0.5 * (predicted - target).square().mean() | |
| if not torch.isfinite(loss): | |
| raise RuntimeError(f'Nonfinite training loss at epoch {epoch}') | |
| loss.backward() | |
| optimizer.step() | |
| last_loss = float(loss.detach()) | |
| total += last_loss * len(u) | |
| seen += len(u) | |
| if seen != 600: | |
| raise RuntimeError(f'Only {seen} training samples processed') | |
| if epoch_index > 0 and epoch_index % tr['scheduler_frequency'] == 0: | |
| scheduler.step() | |
| row = {'epoch': epoch, 'train_half_mse': total / seen, | |
| 'train_mse': 2 * total / seen, 'last_batch_half_mse': last_loss, | |
| 'learning_rate': lr_used, 'elapsed_seconds': time.monotonic() - started} | |
| val_summary = None | |
| if epoch_index % tr['validation_frequency'] == 0 or epoch == tr['epochs']: | |
| val_summary, *_ = evaluate(model, val_u, val_y, times, tr['batch_size']) | |
| row.update(validation_mse=val_summary['mse'], | |
| validation_rmse_mean=val_summary['trajectory_rmse_mean'], | |
| validation_rmse_std=val_summary['trajectory_rmse_std_ddof1']) | |
| if val_summary['mse'] < best_mse: | |
| best_mse, best_epoch = val_summary['mse'], epoch | |
| checkpoint_save(cfg['paths']['checkpoint'], { | |
| 'format_version': 1, 'model_config': cfg['model'], | |
| 'model_state_dict': model.state_dict(), 'epoch': epoch, | |
| 'validation_mse': best_mse, 'validation_metrics': val_summary, | |
| 'selection_split': 'validation[600:800]', | |
| 'data_sha256': data_sha, 'seed': cfg['seed']}) | |
| save_json(result_dir / 'best_validation_metrics.json', | |
| {'epoch': epoch, 'split': 'validation', **val_summary}) | |
| print(f'VALIDATION epoch={epoch} mse={val_summary["mse"]:.9e} ' | |
| f'RMSE_mean={val_summary["trajectory_rmse_mean"]:.9e} ' | |
| f'RMSE_std={val_summary["trajectory_rmse_std_ddof1"]:.9e} ' | |
| f'best_epoch={best_epoch}', flush=True) | |
| writer.writerow(row) | |
| print(f'epoch={epoch:04d}/5000 train_loss(half_MSE)={total / seen:.9e} ' | |
| f'train_MSE={2 * total / seen:.9e} lr={lr_used:.6e} ' | |
| f'samples={seen} elapsed={row["elapsed_seconds"]:.1f}s', flush=True) | |
| if epoch % tr['checkpoint_frequency'] == 0 or epoch == tr['epochs']: | |
| checkpoint_save(cfg['paths']['last_checkpoint'], { | |
| 'model_config': cfg['model'], 'model_state_dict': model.state_dict(), | |
| 'optimizer_state_dict': optimizer.state_dict(), | |
| 'scheduler_state_dict': scheduler.state_dict(), 'epoch': epoch, | |
| 'best_validation_mse': best_mse, 'best_epoch': best_epoch, | |
| 'torch_rng': torch.get_rng_state(), 'cuda_rng': torch.cuda.get_rng_state_all(), | |
| 'data_sha256': data_sha}) | |
| summary = {'status': 'completed', 'completed_epochs': tr['epochs'], | |
| 'batches_per_epoch': len(loader), 'training_samples_per_epoch': 600, | |
| 'best_epoch': best_epoch, 'best_validation_mse': best_mse, | |
| 'selection_split': 'validation[600:800]', 'test_used_for_selection': False, | |
| 'checkpoint': str(cfg['paths']['checkpoint']), 'data_sha256': data_sha, | |
| 'elapsed_seconds': time.monotonic() - started, | |
| 'slurm_job_id': os.environ.get('SLURM_JOB_ID')} | |
| save_json(result_dir / 'training_summary.json', summary) | |
| update_run_manifest(cfg, status='training_completed', completed_epochs=tr['epochs'], | |
| best_epoch=best_epoch, best_validation_mse=best_mse) | |
| print('FORMAL TRAINING COMPLETE', summary, flush=True) | |
| if __name__ == '__main__': | |
| main() | |