-
Notifications
You must be signed in to change notification settings - Fork 3
Beamline neural network reconstruction training #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
simonge
wants to merge
37
commits into
master
Choose a base branch
from
beamline_training
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
08586ff
Added files
simonge df1b38e
Update
simonge 810cc3b
Make canvas name more useful
simonge 0973e5d
Add beam generator macro
simonge 1323fe0
Update analysis
simonge 6329372
Add phasespace scripts
simonge 67d765c
Change name of functors
simonge 3afabcb
Remove image and acceptance benchmarks for now
simonge 99633c2
Prepare for CI testing
simonge 0deb701
Remove other benchmarks
simonge 163341b
Add missing config.yml
simonge 7a58355
Correct typo
simonge 5cd62be
Re-enable other benchmarks and update cores
simonge 312ca42
Add some pass fail checks
simonge 02e0c0e
Set sim and analysis dir by variable
simonge e39d0f9
Revert exclusion of other benchmarks
simonge 67d4206
Review suggestions
simonge 81fad99
Snakefile: fix for out-of-tree running
veprbl 6b21ff1
Snakefile restore indentation
veprbl b18bf3f
Add header to inputs too
simonge c7e268f
Add low-q2 phase space electron tests
simonge c964d40
Add acceptance sim
simonge 8b98ed6
Make simulation run with correct range and 1000x faster
simonge 31fe8ef
Add outputs from script
simonge ccddf53
Change code inputs to workflow source path
simonge f44d690
rename phasespace to acceptance
simonge 4df2119
Remove unused code
simonge 96e8b50
Define both simulations in the yml
simonge 40d4fe8
Merge remote-tracking branch 'origin/master' into beamline_acceptance
simonge 6878af8
Add entry fraction plot
simonge 09f8681
Make filtering more robust
simonge 40e1fa9
Change entry limit warning
simonge 158c8ef
Add reconstruction training based on beampipe exit
simonge d937fdb
Update model and atempt to setup snakemake
simonge e5a4ab8
Fix snakemane rule and silence npsim info
simonge 13030cc
Fix snakemake attribute
simonge a208cd7
Update benchmarks/beamline/Snakefile
simonge File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import uproot | ||
import awkward as ak | ||
|
||
def create_arrays(dataFiles,beamEnergy=18): | ||
|
||
# List of branches to load | ||
branches = ["features","targets"] | ||
|
||
# Load data from concatenated list of files | ||
data = uproot.concatenate([f"{file}:events" for file in dataFiles], branches, library="ak") | ||
|
||
input_data = data["features"] | ||
target_data = data["targets"]/beamEnergy | ||
|
||
return input_data, target_data |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import torch | ||
import torch.nn as nn | ||
import torch.optim as optim | ||
import numpy as np | ||
|
||
class ProjectToX0Plane(nn.Module): | ||
def forward(self, x): | ||
# x shape: (batch, 6) -> [x, y, z, px, py, pz] | ||
x0 = x[:, 0] | ||
y0 = x[:, 1] | ||
z0 = x[:, 2] | ||
px = x[:, 3] | ||
py = x[:, 4] | ||
pz = x[:, 5] | ||
|
||
# Avoid division by zero for px | ||
eps = 1e-8 | ||
px_safe = torch.where(px.abs() < eps, eps * torch.sign(px) + eps, px) | ||
t = -x0 / px_safe | ||
|
||
y_proj = y0 + py * t | ||
z_proj = z0 + pz * t | ||
|
||
# Output: [y_proj, z_proj, px, pz] | ||
return torch.stack([y_proj, z_proj, px, pz], dim=1) | ||
|
||
class RegressionModel(nn.Module): | ||
def __init__(self): | ||
super(RegressionModel, self).__init__() | ||
self.project_to_x0 = ProjectToX0Plane() | ||
self.fc1 = nn.Linear(4, 512) | ||
self.fc2 = nn.Linear(512, 64) | ||
self.fc4 = nn.Linear(64, 3) | ||
|
||
# Normalization parameters | ||
self.input_mean = nn.Parameter(torch.zeros(4), requires_grad=False) | ||
self.input_std = nn.Parameter(torch.ones(4), requires_grad=False) | ||
self.output_mean = nn.Parameter(torch.zeros(3), requires_grad=False) | ||
self.output_std = nn.Parameter(torch.ones(3), requires_grad=False) | ||
|
||
def forward(self, x): | ||
# Apply projection and normalization | ||
x = self.project_to_x0(x) | ||
x = (x - self.input_mean) / self.input_std | ||
|
||
# Pass through the fully connected layers | ||
x = self._core_forward(x) | ||
|
||
# Denormalize outputs | ||
x = x * self.output_std + self.output_mean | ||
return x | ||
|
||
def _core_forward(self, x): | ||
# Core fully connected layers | ||
x = torch.tanh(self.fc1(x)) | ||
x = torch.tanh(self.fc2(x)) | ||
x = self.fc4(x) | ||
return x | ||
|
||
def adapt(self, input_data, output_data): | ||
# Compute normalization parameters from training data | ||
self.input_mean.data = torch.tensor(input_data.mean(axis=0), dtype=torch.float32) | ||
self.input_std.data = torch.tensor(input_data.std(axis=0), dtype=torch.float32) | ||
self.output_mean.data = torch.tensor(output_data.mean(axis=0), dtype=torch.float32) | ||
self.output_std.data = torch.tensor(output_data.std(axis=0), dtype=torch.float32) | ||
|
||
def preprocess_data(model, data_loader): | ||
inputs = data_loader.dataset.tensors[0] | ||
targets = data_loader.dataset.tensors[1] | ||
|
||
# Apply projection | ||
projected_inputs = ProjectToX0Plane()(inputs) | ||
|
||
# Compute normalization parameters | ||
model.adapt(projected_inputs.detach().numpy(), targets.detach().numpy()) | ||
|
||
# Normalize inputs and targets | ||
normalized_inputs = (projected_inputs - model.input_mean) / model.input_std | ||
normalized_targets = (targets - model.output_mean) / model.output_std | ||
|
||
# Replace the dataset with preprocessed data | ||
data_loader.dataset.tensors = (normalized_inputs, normalized_targets) | ||
|
||
def makeModel(): | ||
# Create the model | ||
model = RegressionModel() | ||
# Define the optimizer | ||
optimizer = optim.Adam(model.parameters(), lr=0.0001) | ||
# Define the loss function | ||
criterion = nn.MSELoss() | ||
|
||
return model, optimizer, criterion | ||
|
||
def trainModel(epochs, train_loader, val_loader): | ||
|
||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | ||
print(f"Using device: {device}") | ||
|
||
model, optimizer, criterion = makeModel() | ||
model.to(device) | ||
|
||
# Verify that the model parameters are on the GPU | ||
# for name, param in model.named_parameters(): | ||
# print(f"{name} is on {param.device}") | ||
|
||
# Preprocess training and validation data | ||
preprocess_data(model, train_loader) | ||
preprocess_data(model, val_loader) | ||
|
||
for epoch in range(epochs): | ||
model.train() | ||
running_loss = 0.0 | ||
for inputs, targets in train_loader: | ||
# inputs, targets = inputs.to(device), targets.to(device) | ||
optimizer.zero_grad() | ||
outputs = model._core_forward(inputs) | ||
loss = criterion(outputs, targets) | ||
loss.backward() | ||
optimizer.step() | ||
running_loss += loss.item() * inputs.size(0) | ||
|
||
epoch_loss = running_loss / len(train_loader.dataset) | ||
# print(f"Epoch [{epoch+1}/{epochs}], Loss: {epoch_loss:.4f}") | ||
|
||
|
||
# Validation step | ||
model.eval() | ||
val_loss = 0.0 | ||
with torch.no_grad(): | ||
for val_inputs, val_targets in val_loader: | ||
# val_inputs, val_targets = val_inputs.to(device), val_targets.to(device) | ||
val_outputs = model._core_forward(val_inputs) | ||
val_loss += criterion(val_outputs, val_targets).item() * val_inputs.size(0) | ||
# val_outputs = model(val_input) | ||
# val_loss = criterion(val_outputs, val_target) | ||
|
||
val_loss /= len(val_loader.dataset) | ||
|
||
print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss}, Val Loss: {val_loss}") | ||
|
||
return model |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import torch | ||
import argparse | ||
from ProcessData import create_arrays | ||
from torch.utils.data import DataLoader, TensorDataset | ||
from RegressionModel import makeModel, trainModel | ||
|
||
# Parse arguments | ||
parser = argparse.ArgumentParser(description='Train a regression model for the Tagger.') | ||
parser.add_argument('--dataFiles', type=str, nargs='+', help='Path to the data files') | ||
parser.add_argument('--outModelFile', type=str, default="regression_model.onnx", help='Output file for the trained model') | ||
parser.add_argument('--batchSize', type=int, default=4096, help='Batch size for training') | ||
parser.add_argument('--epochs', type=int, default=100, help='Number of epochs for training') | ||
args = parser.parse_args() | ||
|
||
input_data, target_data = create_arrays(args.dataFiles) | ||
|
||
# print(f"Input data shape: {input_data.shape}") | ||
# print(f"Target data shape: {target_data.shape}") | ||
|
||
torch_input_data = torch.tensor(input_data) | ||
torch_target_data = torch.tensor(target_data) | ||
|
||
print(f"Input data shape: {torch_input_data.shape}") | ||
print(f"Target data shape: {torch_target_data.shape}") | ||
|
||
# Split data into training and validation sets | ||
validation_fraction = 0.25 | ||
split_index = int(len(torch_input_data) * (1 - validation_fraction)) | ||
|
||
val_input_data = torch_input_data[split_index:] | ||
val_target_data = torch_target_data[split_index:] | ||
train_input_data = torch_input_data[:split_index] | ||
train_target_data = torch_target_data[:split_index] | ||
|
||
# Create TensorDatasets | ||
train_dataset = TensorDataset(train_input_data, train_target_data) | ||
val_dataset = TensorDataset(val_input_data, val_target_data) | ||
|
||
# Create DataLoaders | ||
train_loader = DataLoader(train_dataset, batch_size=args.batchSize, shuffle=True ) | ||
val_loader = DataLoader(val_dataset, batch_size=args.batchSize, shuffle=False) | ||
|
||
print(f"Training data: {len(train_input_data)} samples") | ||
|
||
model = trainModel(args.epochs, train_loader, val_loader) | ||
|
||
# Save the trained model to ONNX format | ||
|
||
dummy_input = torch_input_data[0].unsqueeze(0) # Create a dummy input for the model | ||
|
||
torch.onnx.export(model, dummy_input, args.outModelFile, | ||
input_names=['input'], output_names=['output'], | ||
dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}) | ||
|
||
print(f"Model has been saved to {args.outModelFile}") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.