RRF / model.py
antonypamo's picture
Upload 8 files
e73b39d verified
Raw
History Blame
43.6 kB
# Synthesized wrapper model file (inspect and adapt before use)
import torch
import torch.nn as nn
# --- extracted class 1 ---
class LossWeights:
lambda_task: float = 1.0
lambda_res: float = 0.5
lambda_ent: float = 0.2
# --- extracted class 2 ---
class RRF_Ultra_CNN(nn.Module):
def __init__(self, input_dim=1, output_dim=1):
super(RRF_Ultra_CNN, self).__init__()
self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=3, padding=1)
self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
self.fc1 = nn.Linear(128*160, 256)
self.fc2 = nn.Linear(256, output_dim)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = torch.flatten(x, 1)
x = F.relu(self.fc1(x))
return torch.sigmoid(self.fc2(x))
# --- extracted class 3 ---
class SavantRRF_Gauge(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(SavantRRF_Gauge, self).__init__()
self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=3, padding=1)
self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
self.conv3 = nn.Conv1d(128, 256, kernel_size=3, padding=1)
self.dropout = nn.Dropout(0.25)
# The input size to fc1 is based on the output size of conv3.
# Assuming input sequence length is 160, after 3 conv layers with kernel_size 3 and padding 1,
# the sequence length remains 160. 256 channels * 160 length = 40960.
self.fc1 = nn.Linear(256*160, 512) # Corrected input size based on sequence_length=160
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, output_dim)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.relu(self.conv3(x))
x = torch.flatten(x, 1)
x = self.dropout(x)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return torch.sigmoid(self.fc3(x))
# --- extracted class 4 ---
class DiracGraphConv(nn.Module):
def __init__(self, in_dim: int, out_dim: int, alpha: float = 1.0, bias: bool = True):
super().__init__()
self.lin = nn.Linear(in_dim, out_dim, bias=bias)
self.alpha = nn.Parameter(torch.tensor(alpha, dtype=torch.float32))
self.bias_edge = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))
@staticmethod
def cosine_corr(z_i: torch.Tensor, z_j: torch.Tensor, eps: float = 1e-9) -> torch.Tensor:
num = (z_i * z_j).sum(dim=-1)
den = torch.clamp(z_i.norm(dim=-1) * z_j.norm(dim=-1), min=eps)
return num / den
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
N = x.size(0)
row, col = edge_index
corr = self.cosine_corr(z[row], z[col])
logits = self.alpha * corr + self.bias_edge
device = x.device
E = row.size(0)
ones = torch.ones(E, device=device)
max_per_row = torch.full((N,), -1e9, device=device)
max_per_row = max_per_row.index_put((row,), logits, accumulate=False).scatter_reduce_(0, row, logits, reduce="amax")
logits_centered = logits - max_per_row[row]
exp_logits = torch.exp(logits_centered)
denom = torch.zeros(N, device=device).index_add_(0, row, exp_logits)
attn = exp_logits / (denom[row] + 1e-9)
deg = torch.zeros(N, device=device).index_add_(0, row, ones)
norm = 1.0 / torch.clamp(deg[row], min=1.0)
msgs = norm.unsqueeze(-1) * attn.unsqueeze(-1) * x[col]
out = torch.zeros_like(x).index_add_(0, row, msgs)
return self.lin(out)
# --- extracted class 5 ---
class GNNDiracRRF(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int, out_dim: int, num_layers: int, z_dim: int,
alpha_attn: float = 1.0, dropout: float = 0.1):
super().__init__()
self.z_dim = z_dim
self.layers = nn.ModuleList()
self.layers.append(DiracGraphConv(in_dim, hidden_dim, alpha=alpha_attn))
for _ in range(num_layers - 2):
self.layers.append(DiracGraphConv(hidden_dim, hidden_dim, alpha=alpha_attn))
self.layers.append(DiracGraphConv(hidden_dim, out_dim, alpha=alpha_attn))
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
h = x
for i, layer in enumerate(self.layers):
h = layer(h, edge_index, z)
if i < len(self.layers) - 1:
h = F.gelu(h)
h = self.dropout(h)
return h
# --- extracted class 6 ---
class LossWeights:
lambda_task: float = 1.0
lambda_res: float = 0.5
lambda_ent: float = 0.2
# --- extracted class 7 ---
class IcosahedralRRF(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, gnn_num_layers=2, gnn_z_dim=16, gnn_alpha_attn=1.0, gnn_dropout=0.1):
super(IcosahedralRRF, self).__init__()
# 12 nodos gauge
self.nodes = nn.ModuleList([
SavantRRF_Gauge(input_dim, hidden_dim, output_dim) for _ in range(12)
])
# Núcleo ético
# The input to ethical_core is the concatenation of the outputs of the 12 gauge nodes.
# Each gauge node outputs a tensor of shape [batch_size, output_dim].
# Concatenating these along dim=1 results in a shape [batch_size, 12 * output_dim].
self.ethical_core = nn.Linear(12 * output_dim, output_dim)
# Subconsciente (dodecaedro) using GNNDiracRRF
# The input dimension (in_dim) for the GNN should match the feature dimension of its input nodes.
# There's ambiguity in the original code about what the GNN's nodes and features are.
# Interpretation 1 (based on original code passing 'regulated'): GNN operates on 'batch_size' nodes, with 'output_dim' features. in_dim = output_dim.
# Interpretation 2 (more conventional for graph on icosahedron/dodecahedron): GNN operates on 12 or 20 nodes, with features derived from gauge outputs.
# Let's assume interpretation 2, where the GNN operates on the 12 gauge nodes.
# The features for each of these 12 nodes would be the output of the corresponding gauge node, shape [batch_size, output_dim].
# For a GNN layer expecting [num_nodes, in_channels], the input should be [12, output_dim] per batch item.
# This means the GNN's in_dim should be output_dim. This matches the current GNN init below.
# The GNN's out_dim should match the desired output feature dimension per node (e.g., output_dim).
# The number of nodes for the GNN is 12 (for icosahedral).
# Let's define the memory_map GNN assuming it operates on the 12 gauge nodes.
# The input features to the GNN will be the outputs of the 12 gauge nodes.
# Each gauge node outputs a tensor of shape [batch_size, output_dim].
# We will treat output_dim as the feature dimension for the GNN nodes (the 12 gauge nodes).
# So, in_dim for GNN = output_dim.
# The GNN will output features for each of the 12 nodes. Let's assume out_dim for GNN is also output_dim.
self.memory_map = GNNDiracRRF(in_dim=output_dim, # Feature dimension for GNN nodes (output_dim of gauge nodes)
hidden_dim=hidden_dim,
out_dim=output_dim, # Output feature dimension per GNN node
num_layers=gnn_num_layers,
z_dim=gnn_z_dim,
alpha_attn=gnn_alpha_attn,
dropout=gnn_dropout)
def forward(self, x, edge_index=None, z=None):
# x is the input to the gauge nodes, shape [batch_size, input_dim, sequence_length]
outputs = [node(x) for node in self.nodes]
# outputs is a list of 12 tensors, each [batch_size, output_dim]
# Concatenate outputs for the ethical core
concat = torch.cat(outputs, dim=1) # [batch_size, 12 * output_dim]
regulated = torch.sigmoid(self.ethical_core(concat)) # [batch_size, output_dim]
# GNN operation on the 12 gauge nodes
if edge_index is not None and z is not None:
# Prepare input for the GNN: Features for the 12 nodes (the gauge node outputs).
# Stack the outputs to get [batch_size, 12, output_dim]
stacked_outputs = torch.stack(outputs, dim=1) # [batch_size, 12, output_dim]
# Reshape for GNN input: [num_nodes, in_channels] = [12, output_dim] per batch item.
# Need to process batch items. Simplest is to iterate.
# A more efficient way is to use torch_geometric.data.Batch
gnn_outputs_list = []
for i in range(stacked_outputs.size(0)):
# GNN input features for this batch item: [12, output_dim]
gnn_input_features_i = stacked_outputs[i]
# Ensure edge_index and z are on the correct device
edge_index_i = edge_index.to(x.device)
z_i = z.to(x.device)
# GNN forward pass for one batch item
gnn_output_i = self.memory_map(gnn_input_features_i, edge_index_i, z_i) # [12, output_dim]
gnn_outputs_list.append(gnn_output_i)
# Stack GNN outputs back into a batch tensor: [batch_size, 12, output_dim]
gnn_outputs_stacked = torch.stack(gnn_outputs_list, dim=0)
# Now, how to combine the GNN output [batch_size, 12, output_dim] with the 'regulated' output [batch_size, output_dim]?
# The original model returned just 'regulated'.
# A simple approach is to maybe combine them, e.g., add, concatenate, or use the GNN output as a modulation.
# Let's stick to returning the aggregated GNN output as the final output when GNN is used.
# This changes the model's behavior compared to the original.
# Alternative: The GNN output modulates the 'regulated' output.
# E.g., regulated * sigmoid(aggregated_gnn_output) or similar.
# Let's stick to returning the aggregated GNN output when edge_index and z are provided,
# and the original 'regulated' output otherwise. This seems the most direct path based on the conditional in the original forward.
# Aggregate the 12 nodes' outputs from the GNN
aggregated_gnn_output = gnn_outputs_stacked.mean(dim=1) # [batch_size, output_dim]
return aggregated_gnn_output # [batch_size, output_dim]
else:
# If edge_index and z are not provided, return the output of the ethical core as before.
return regulated
# --- extracted class 8 ---
class RRF_Ultra_CNN(nn.Module):
def __init__(self, input_dim=1, output_dim=1):
super(RRF_Ultra_CNN, self).__init__()
self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=3, padding=1)
self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
self.fc1 = nn.Linear(128*160, 256)
self.fc2 = nn.Linear(256, output_dim)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = torch.flatten(x, 1)
x = F.relu(self.fc1(x))
return torch.sigmoid(self.fc2(x))
# --- extracted class 9 ---
class SavantRRF_Gauge(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(SavantRRF_Gauge, self).__init__()
self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=3, padding=1)
self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
self.conv3 = nn.Conv1d(128, 256, kernel_size=3, padding=1)
self.dropout = nn.Dropout(0.25)
# The input size to fc1 is based on the output size of conv3.
# Assuming input sequence length is 160, after 3 conv layers with kernel_size 3 and padding 1,
# the sequence length remains 160. 256 channels * 160 length = 40960.
self.fc1 = nn.Linear(256*160, 512) # Corrected input size based on sequence_length=160
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, output_dim)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.relu(self.conv3(x))
x = torch.flatten(x, 1)
x = self.dropout(x)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return torch.sigmoid(self.fc3(x))
# --- extracted class 10 ---
class DiracGraphConv(nn.Module):
def __init__(self, in_dim: int, out_dim: int, alpha: float = 1.0, bias: bool = True):
super().__init__()
self.lin = nn.Linear(in_dim, out_dim, bias=bias)
self.alpha = nn.Parameter(torch.tensor(alpha, dtype=torch.float32))
self.bias_edge = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))
@staticmethod
def cosine_corr(z_i: torch.Tensor, z_j: torch.Tensor, eps: float = 1e-9) -> torch.Tensor:
num = (z_i * z_j).sum(dim=-1)
den = torch.clamp(z_i.norm(dim=-1) * z_j.norm(dim=-1), min=eps)
return num / den
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
N = x.size(0)
row, col = edge_index
corr = self.cosine_corr(z[row], z[col])
logits = self.alpha * corr + self.bias_edge
device = x.device
E = row.size(0)
ones = torch.ones(E, device=device)
max_per_row = torch.full((N,), -1e9, device=device)
max_per_row = max_per_row.index_put((row,), logits, accumulate=False).scatter_reduce_(0, row, logits, reduce="amax")
logits_centered = logits - max_per_row[row]
exp_logits = torch.exp(logits_centered)
denom = torch.zeros(N, device=device).index_add_(0, row, exp_logits)
attn = exp_logits / (denom[row] + 1e-9)
deg = torch.zeros(N, device=device).index_add_(0, row, ones)
norm = 1.0 / torch.clamp(deg[row], min=1.0)
msgs = norm.unsqueeze(-1) * attn.unsqueeze(-1) * x[col]
out = torch.zeros_like(x).index_add_(0, row, msgs)
return self.lin(out)
# --- extracted class 11 ---
class GNNDiracRRF(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int, out_dim: int, num_layers: int, z_dim: int,
alpha_attn: float = 1.0, dropout: float = 0.1):
super().__init__()
self.z_dim = z_dim
self.layers = nn.ModuleList()
self.layers.append(DiracGraphConv(in_dim, hidden_dim, alpha=alpha_attn))
for _ in range(num_layers - 2):
self.layers.append(DiracGraphConv(hidden_dim, hidden_dim, alpha=alpha_attn))
self.layers.append(DiracGraphConv(hidden_dim, out_dim, alpha=alpha_attn))
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
h = x
for i, layer in enumerate(self.layers):
h = layer(h, edge_index, z)
if i < len(self.layers) - 1:
h = F.gelu(h)
h = self.dropout(h)
return h
# --- extracted class 12 ---
class LossWeights:
lambda_task: float = 1.0
lambda_res: float = 0.5
lambda_ent: float = 0.2
# --- extracted class 13 ---
class IcosahedralRRF(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, gnn_num_layers=2, gnn_z_dim=16, gnn_alpha_attn=1.0, gnn_dropout=0.1):
super(IcosahedralRRF, self).__init__()
# 12 nodos gauge
self.nodes = nn.ModuleList([
SavantRRF_Gauge(input_dim, hidden_dim, output_dim) for _ in range(12)
])
# Núcleo ético
# The input to ethical_core is the concatenation of the outputs of the 12 gauge nodes.
# Each gauge node outputs a tensor of shape [batch_size, output_dim].
# Concatenating these along dim=1 results in a shape [batch_size, 12 * output_dim].
self.ethical_core = nn.Linear(12 * output_dim, output_dim)
# Subconsciente (dodecaedro) using GNNDiracRRF
# The input dimension (in_dim) for the GNN should match the feature dimension of its input nodes.
# There's ambiguity in the original code about what the GNN's nodes and features are.
# Interpretation 1 (based on original code passing 'regulated'): GNN operates on 'batch_size' nodes, with 'output_dim' features. in_dim = output_dim.
# Interpretation 2 (more conventional for graph on icosahedron/dodecahedron): GNN operates on 12 or 20 nodes, with features derived from gauge outputs.
# Let's assume interpretation 2, where the GNN operates on the 12 gauge nodes.
# The features for each of these 12 nodes would be the output of the corresponding gauge node, shape [batch_size, output_dim].
# For a GNN layer expecting [num_nodes, in_channels], the input should be [12, output_dim] per batch item.
# This means the GNN's in_dim should be output_dim. This matches the current GNN init below.
# The GNN's out_dim should match the desired output feature dimension per node (e.g., output_dim).
# The number of nodes for the GNN is 12 (for icosahedral).
# Let's define the memory_map GNN assuming it operates on the 12 gauge nodes.
# The input features to the GNN will be the outputs of the 12 gauge nodes.
# Each gauge node outputs a tensor of shape [batch_size, output_dim].
# We will treat output_dim as the feature dimension for the GNN nodes (the 12 gauge nodes).
# So, in_dim for GNN = output_dim.
# The GNN will output features for each of the 12 nodes. Let's assume out_dim for GNN is also output_dim.
self.memory_map = GNNDiracRRF(in_dim=output_dim, # Feature dimension for GNN nodes (output_dim of gauge nodes)
hidden_dim=hidden_dim,
out_dim=output_dim, # Output feature dimension per GNN node
num_layers=gnn_num_layers,
z_dim=gnn_z_dim,
alpha_attn=gnn_alpha_attn,
dropout=gnn_dropout)
def forward(self, x, edge_index=None, z=None):
# x is the input to the gauge nodes, shape [batch_size, input_dim, sequence_length]
outputs = [node(x) for node in self.nodes]
# outputs is a list of 12 tensors, each [batch_size, output_dim]
# Concatenate outputs for the ethical core
concat = torch.cat(outputs, dim=1) # [batch_size, 12 * output_dim]
regulated = torch.sigmoid(self.ethical_core(concat)) # [batch_size, output_dim]
# GNN operation on the 12 gauge nodes
if edge_index is not None and z is not None:
# Prepare input for the GNN: Features for the 12 nodes (the gauge node outputs).
# Stack the outputs to get [batch_size, 12, output_dim]
stacked_outputs = torch.stack(outputs, dim=1) # [batch_size, 12, output_dim]
# Reshape for GNN input: [num_nodes, in_channels] = [12, output_dim] per batch item.
# Need to process batch items. Simplest is to iterate.
# A more efficient way is to use torch_geometric.data.Batch
gnn_outputs_list = []
for i in range(stacked_outputs.size(0)):
# GNN input features for this batch item: [12, output_dim]
gnn_input_features_i = stacked_outputs[i]
# Ensure edge_index and z are on the correct device
edge_index_i = edge_index.to(x.device)
z_i = z.to(x.device)
# GNN forward pass for one batch item
gnn_output_i = self.memory_map(gnn_input_features_i, edge_index_i, z_i) # [12, output_dim]
gnn_outputs_list.append(gnn_output_i)
# Stack GNN outputs back into a batch tensor: [batch_size, 12, output_dim]
gnn_outputs_stacked = torch.stack(gnn_outputs_list, dim=0)
# Now, how to combine the GNN output [batch_size, 12, output_dim] with the 'regulated' output [batch_size, output_dim]?
# The original model returned just 'regulated'.
# A simple approach is to maybe combine them, e.g., add, concatenate, or use the GNN output as a modulation.
# Let's stick to returning the aggregated GNN output as the final output when GNN is used.
# This changes the model's behavior compared to the original.
# Alternative: The GNN output modulates the 'regulated' output.
# E.g., regulated * sigmoid(aggregated_gnn_output) or similar.
# Let's stick to returning the aggregated GNN output when edge_index and z are provided,
# and the original 'regulated' output otherwise. This seems the most direct path based on the conditional in the original forward.
# Aggregate the 12 nodes' outputs from the GNN
aggregated_gnn_output = gnn_outputs_stacked.mean(dim=1) # [batch_size, output_dim]
return aggregated_gnn_output # [batch_size, output_dim]
else:
# If edge_index and z are not provided, return the output of the ethical core as before.
return regulated
# --- extracted class 14 ---
class LossWeights:
lambda_task: float = 1.0
lambda_res: float = 0.5
lambda_ent: float = 0.2
# --- extracted class 15 ---
class IcosahedralRRFDataset(InMemoryDataset):
def __init__(self, num_graphs: int = 64, k_modes: int = 16, feat_dim: int = 8,
task_type: str = 'classification', split: str = 'train', transform=None, pre_transform=None):
super().__init__('.', transform, pre_transform)
self.task_type = task_type
self.num_graphs = num_graphs
self.k_modes = k_modes
self.feat_dim = feat_dim
# Generate graphs and process them
data_list = []
rng = np.random.default_rng(42 if split == 'train' else (43 if split == 'val' else 44))
for i in range(num_graphs):
G = nx.icosahedral_graph()
n_nodes = G.number_of_nodes()
# Build Dirac operator and compute spectral modes
D = build_dirac_operator(G, normalize=True)
# Use the modified dirac_eigendecomp that uses np.linalg.eigh
vals, vecs = dirac_eigendecomp(D, k=k_modes)
Z = node_spectral_coords_from_dirac(vecs, n_nodes) # N x k
# Get edge index
edge_list = list(G.edges())
edge_index = torch.tensor(edge_list, dtype=torch.long).t().contiguous()
# Add reverse edges for undirected graph
row, col = edge_index
edge_index = torch.cat([edge_index, torch.stack([col, row], dim=0)], dim=1)
# Generate synthetic node features (x) and labels (y)
# Features: [n_nodes, feat_dim]
x = torch.randn(n_nodes, feat_dim, dtype=torch.float32)
# Labels: based on task_type
if task_type == 'classification':
# Example: Binary classification based on a simple rule, e.g., sum of features > threshold
threshold = 0.0 # Example threshold
y = (x.sum(dim=-1) > threshold).long() # [n_nodes]
elif task_type == 'regression':
# Example: Regression target based on sum of features
y = x.sum(dim=-1) # [n_nodes]
else:
raise ValueError("task_type must be 'classification' or 'regression'")
# Create Data object
# Note: The IcosahedralRRF model expects input 'x' as [batch_size, input_dim, sequence_length],
# edge_index [2, num_edges], and z [num_nodes, z_dim].
# The IcosahedralRRFDataset provides batch.x [num_nodes, feat_dim], batch.edge_index [2, num_edges], and batch.U [num_nodes, k_modes].
# There is a mismatch in the expected input format for the IcosahedralRRF model's forward pass when using the DataLoader.
# The IcosahedralRRF expects a single batch tensor `x` for the gauge nodes, and graph data (edge_index, z) for the GNN part which operates on gauge outputs.
# The IcosahedralRRFDataset provides node features `batch.x` that are intended as features *for the graph nodes themselves*, not as input to the gauge nodes.
# The current IcosahedralRRF forward pass processes a single input `x` [batch_size, input_dim, sequence_length] through all gauge nodes.
# The GNN then operates on the *outputs* of these gauge nodes, using the provided edge_index and z.
# To use the IcosahedralRRFDataset with the current IcosahedralRRF model structure,
# we need to map the dataset's structure to the model's expectations.
# The dataset provides graphs, each with nodes (typically 12 for icosahedral), node features (batch.x), edge_index, and spectral coords (batch.U).
# The IcosahedralRRF model has 12 gauge nodes, each designed to process a sequence [input_dim, sequence_length].
# It seems there is a conceptual mismatch in how the IcosahedralRRFDataset is structured (graph-centric with node features)
# and how the IcosahedralRRF model processes input (sequence-centric through gauge nodes first).
# Alternative Interpretation: The IcosahedralRRFDataset is meant to provide data where each *graph* is a sample in the batch.
# batch.x would be the concatenated node features for all graphs in the batch: [total_num_nodes_in_batch, feat_dim].
# batch.edge_index would be the block-diagonal edge indices for all graphs: [2, total_num_edges_in_batch].
# batch.U would be the concatenated spectral coordinates for all nodes: [total_num_nodes_in_batch, k_modes].
# In this case, the input to the IcosahedralRRF model's forward pass is still expected to be a single tensor `x` for the gauge nodes.
# The IcosahedralRRFDataset does *not* provide this `x` input directly in the expected format.
# There is a fundamental incompatibility in how the IcosahedralRRFDataset provides data (graph-batching)
# and how the IcosahedralRRF model expects input (single batch of sequences + graph data for GNN).
# To make this cell runnable, we need to either:
# 1. Modify the IcosahedralRRF model's forward pass to handle graph batches from DataLoader.
# 2. Modify the IcosahedralRRFDataset or create a custom Dataset/DataLoader that provides data in the format expected by the IcosahedralRRF model.
# 3. Use a simplified evaluation approach that aligns with the synthetic data generation method used in the training loop (single batch).
# Given the current structure, the simplest approach to get the cell running is to align the evaluation data generation
# with the training data generation (single synthetic batch) and evaluate on that.
# This bypasses the DataLoader incompatibility but doesn't fully test with graph batching.
# Let's revert to generating a single synthetic batch for evaluation, similar to training.
# This requires defining x_val and y_val outside the DataLoader loop.
# Reverting the evaluation loop to use the single synthetic batch approach:
# Check if x_val and y_val are defined (from previous code cell)
if 'x_val' not in locals() or 'y_val' not in locals():
# Generate synthetic validation data if not already defined
val_batch_size = 16 # Example validation batch size
x_val = torch.randn(val_batch_size, input_dim, sequence_length, dtype=torch.float32).to(device)
y_val = torch.randint(0, 2, (val_batch_size,), dtype=torch.long).to(device) # Binary labels
print("Generated synthetic validation data for evaluation.")
# Ensure z and edge_index are on the correct device
if 'z' in locals() and 'edge_index' in locals():
z = z.to(device)
edge_index = edge_index.to(device)
else:
print("⚠️ Warning: Graph data (z, edge_index) not found. Skipping evaluation.")
# Exit the evaluation block if graph data is missing
# break # This will exit the with torch.no_grad(): block - REMOVED/COMMENTED OUT DUE TO SyntaxError
pass # Use pass instead of break to avoid SyntaxError outside a loop
# Forward pass on validation data using the single batch
# Pass the validation input features (x_val), edge index, and spectral coordinates (z) through the model
val_outputs = hybrid_model(x_val, edge_index, z) # Shape: [val_batch_size, output_dim]
# Calculate the validation loss (using BCEWithLogitsLoss as corrected in training)
val_loss = F.binary_cross_entropy_with_logits(val_outputs.squeeze(-1), y_val.float())
# Calculate evaluation metrics (e.g., accuracy for binary classification)
# Convert logits to predicted class (0 or 1)
predicted_classes = (torch.sigmoid(val_outputs.squeeze(-1)) > 0.5).long()
# Calculate accuracy
correct_predictions = (predicted_classes == y_val).sum().item()
accuracy = correct_predictions / val_batch_size
print(f'Validation Loss: {val_loss.item():.4f}, Validation Accuracy: {accuracy:.4f}')
# --- extracted class 16 ---
class DiracGraphConv(nn.Module):
def __init__(self, in_dim: int, out_dim: int, alpha: float = 1.0, bias: bool = True):
super().__init__()
self.lin = nn.Linear(in_dim, out_dim, bias=bias)
self.alpha = nn.Parameter(torch.tensor(alpha, dtype=torch.float32))
self.bias_edge = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))
@staticmethod
def cosine_corr(z_i: torch.Tensor, z_j: torch.Tensor, eps: float = 1e-9) -> torch.Tensor:
num = (z_i * z_j).sum(dim=-1)
den = torch.clamp(z_i.norm(dim=-1) * z_j.norm(dim=-1), min=eps)
return num / den
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
N = x.size(0)
row, col = edge_index
corr = self.cosine_corr(z[row], z[col])
logits = self.alpha * corr + self.bias_edge
device = x.device
E = row.size(0)
ones = torch.ones(E, device=device)
max_per_row = torch.full((N,), -1e9, device=device)
max_per_row = max_per_row.index_put((row,), logits, accumulate=False).scatter_reduce_(0, row, logits, reduce="amax")
logits_centered = logits - max_per_row[row]
exp_logits = torch.exp(logits_centered)
denom = torch.zeros(N, device=device).index_add_(0, row, exp_logits)
attn = exp_logits / (denom[row] + 1e-9)
deg = torch.zeros(N, device=device).index_add_(0, row, ones)
norm = 1.0 / torch.clamp(deg[row], min=1.0)
msgs = norm.unsqueeze(-1) * attn.unsqueeze(-1) * x[col]
out = torch.zeros_like(x).index_add_(0, row, msgs)
return self.lin(out)
# --- extracted class 17 ---
class GNNDiracRRF(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int, out_dim: int, num_layers: int, z_dim: int,
alpha_attn: float = 1.0, dropout: float = 0.1):
super().__init__()
self.z_dim = z_dim
self.layers = nn.ModuleList()
self.layers.append(DiracGraphConv(in_dim, hidden_dim, alpha=alpha_attn))
for _ in range(num_layers - 2):
self.layers.append(DiracGraphConv(hidden_dim, hidden_dim, alpha=alpha_attn))
self.layers.append(DiracGraphConv(hidden_dim, out_dim, alpha=alpha_attn))
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
h = x
for i, layer in enumerate(self.layers):
h = layer(h, edge_index, z)
if i < len(self.layers) - 1:
h = F.gelu(h)
h = self.dropout(h)
return h
# --- extracted class 18 ---
class DiracGraphConv(nn.Module):
def __init__(self, in_dim: int, out_dim: int, alpha: float = 1.0, bias: bool = True):
super().__init__()
self.lin = nn.Linear(in_dim, out_dim, bias=bias)
self.alpha = nn.Parameter(torch.tensor(alpha, dtype=torch.float32))
self.bias_edge = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))
@staticmethod
def cosine_corr(z_i: torch.Tensor, z_j: torch.Tensor, eps: float = 1e-9) -> torch.Tensor:
num = (z_i * z_i).sum(dim=-1) # Corrected dot product: z_i * z_j
den = torch.clamp(z_i.norm(dim=-1) * z_j.norm(dim=-1), min=eps)
return num / den
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
N = x.size(0)
row, col = edge_index
corr = self.cosine_corr(z[row], z[col])
logits = self.alpha * corr + self.bias_edge
device = x.device
E = row.size(0)
ones = torch.ones(E, device=device)
max_per_row = torch.full((N,), -1e9, device=device)
max_per_row = max_per_row.index_put((row,), logits, accumulate=False).scatter_reduce_(0, row, logits, reduce="amax")
logits_centered = logits - max_per_row[row]
exp_logits = torch.exp(logits_centered)
denom = torch.zeros(N, device=device).index_add_(0, row, exp_logits)
attn = exp_logits / (denom[row] + 1e-9)
deg = torch.zeros(N, device=device).index_add_(0, row, ones)
norm = 1.0 / torch.clamp(deg[row], min=1.0)
msgs = norm.unsqueeze(-1) * attn.unsqueeze(-1) * x[col]
out = torch.zeros_like(x).index_add_(0, row, msgs)
return self.lin(out)
# --- extracted class 19 ---
class GNNDiracRRF(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int, out_dim: int, num_layers: int, z_dim: int,
alpha_attn: float = 1.0, dropout: float = 0.1):
super().__init__()
self.z_dim = z_dim
self.layers = nn.ModuleList()
self.layers.append(DiracGraphConv(in_dim, hidden_dim, alpha=alpha_attn))
for _ in range(num_layers - 2):
self.layers.append(DiracGraphConv(hidden_dim, hidden_dim, alpha=alpha_attn))
self.layers.append(DiracGraphConv(hidden_dim, out_dim, alpha=alpha_attn))
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
h = x
for i, layer in enumerate(self.layers):
h = layer(h, edge_index, z)
if i < len(self.layers) - 1:
h = F.gelu(h)
h = self.dropout(h)
return h
# --- extracted class 20 ---
class SavantRRF_Gauge(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(SavantRRF_Gauge, self).__init__()
self.conv1 = nn.Conv1d(input_dim, 64, kernel_size=3, padding=1)
self.conv2 = nn.Conv1d(64, 128, kernel_size=3, padding=1)
self.conv3 = nn.Conv1d(128, 256, kernel_size=3, padding=1)
self.dropout = nn.Dropout(0.25)
# Assuming input sequence length is 160
self.fc1 = nn.Linear(256*160, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, output_dim)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.relu(self.conv3(x))
x = torch.flatten(x, 1)
x = self.dropout(x)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return torch.sigmoid(self.fc3(x))
# --- extracted class 21 ---
class IcosahedralRRF(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, gnn_num_layers=2, gnn_z_dim=16, gnn_alpha_attn=1.0, gnn_dropout=0.1):
super(IcosahedralRRF, self).__init__()
# 12 nodos gauge
self.nodes = nn.ModuleList([
SavantRRF_Gauge(input_dim, hidden_dim, output_dim) for _ in range(12)
])
# Núcleo ético
self.ethical_core = nn.Linear(12 * output_dim, output_dim)
# Subconsciente (dodecaedro/icosaedro) using GNNDiracRRF
# The GNN operates on the 12 gauge node outputs.
# The input features to the GNN are the outputs of the 12 gauge nodes, shape [batch_size, output_dim].
# For GNN layer, input is [num_nodes, in_channels] = [12, output_dim] per batch item.
self.memory_map = GNNDiracRRF(in_dim=output_dim,
hidden_dim=hidden_dim,
out_dim=output_dim,
num_layers=gnn_num_layers,
z_dim=gnn_z_dim,
alpha_attn=gnn_alpha_attn,
dropout=gnn_dropout)
def forward(self, x, edge_index=None, z=None):
# x is the input to the gauge nodes, shape [batch_size, input_dim, sequence_length]
outputs = [node(x) for node in self.nodes]
# outputs is a list of 12 tensors, each [batch_size, output_dim]
# Concatenate outputs for the ethical core
concat = torch.cat(outputs, dim=1) # [batch_size, 12 * output_dim]
regulated = torch.sigmoid(self.ethical_core(concat)) # [batch_size, output_dim]
# GNN operation on the 12 gauge nodes
if edge_index is not None and z is not None:
# Prepare input for the GNN: Features for the 12 nodes (the gauge node outputs).
stacked_outputs = torch.stack(outputs, dim=1) # [batch_size, 12, output_dim]
gnn_outputs_list = []
for i in range(stacked_outputs.size(0)):
gnn_input_features_i = stacked_outputs[i]
edge_index_i = edge_index.to(x.device)
z_i = z.to(x.device)
gnn_output_i = self.memory_map(gnn_input_features_i, edge_index_i, z_i) # [12, output_dim]
gnn_outputs_list.append(gnn_output_i)
gnn_outputs_stacked = torch.stack(gnn_outputs_list, dim=0)
aggregated_gnn_output = gnn_outputs_stacked.mean(dim=1) # [batch_size, output_dim]
return aggregated_gnn_output # [batch_size, output_dim]
else:
return regulated
# --- extracted class 22 ---
class DiracGraphConv(nn.Module):
def __init__(self, in_dim: int, out_dim: int, alpha: float = 1.0, bias: bool = True):
super().__init__()
self.lin = nn.Linear(in_dim, out_dim, bias=bias)
self.alpha = nn.Parameter(torch.tensor(alpha, dtype=torch.float32))
self.bias_edge = nn.Parameter(torch.tensor(0.0, dtype=torch.float32))
@staticmethod
def cosine_corr(z_i: torch.Tensor, z_j: torch.Tensor, eps: float = 1e-9) -> torch.Tensor:
num = (z_i * z_j).sum(dim=-1)
den = torch.clamp(z_i.norm(dim=-1) * z_j.norm(dim=-1), min=eps)
return num / den
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
N = x.size(0)
row, col = edge_index
# Ensure z has correct shape for cosine_corr
# z should have shape [num_nodes, z_dim]
# x has shape [num_nodes, in_dim]
# When called from GNNDiracRRF, num_nodes is 12 (for icosahedral)
# z[row] and z[col] should broadcast correctly with x[col]
corr = self.cosine_corr(z[row], z[col])
logits = self.alpha * corr + self.bias_edge
device = x.device
E = row.size(0)
ones = torch.ones(E, device=device)
# Use scatter_reduce_ to calculate max per row
max_per_row = torch.full((N,), -1e9, device=device)
max_per_row = max_per_row.index_put((row,), logits, accumulate=False).scatter_reduce_(0, row, logits, reduce="amax")
logits_centered = logits - max_per_row[row]
exp_logits = torch.exp(logits_centered)
denom = torch.zeros(N, device=device).index_add_(0, row, exp_logits)
attn = exp_logits / (denom[row] + 1e-9)
deg = torch.zeros(N, device=device).index_add_(0, row, ones)
norm = 1.0 / torch.clamp(deg[row], min=1.0)
msgs = norm.unsqueeze(-1) * attn.unsqueeze(-1) * x[col]
out = torch.zeros_like(x).index_add_(0, row, msgs)
return self.lin(out)
# --- extracted class 23 ---
class GNNDiracRRF(nn.Module):
def __init__(self, in_dim: int, hidden_dim: int, out_dim: int, num_layers: int, z_dim: int,
alpha_attn: float = 1.0, dropout: float = 0.1):
super().__init__()
self.z_dim = z_dim
self.layers = nn.ModuleList()
# Ensure DiracGraphConv is defined before this line
self.layers.append(DiracGraphConv(in_dim, hidden_dim, alpha=alpha_attn))
for _ in range(num_layers - 2):
self.layers.append(DiracGraphConv(hidden_dim, hidden_dim, alpha=alpha_attn))
self.layers.append(DiracGraphConv(hidden_dim, out_dim, alpha=alpha_attn))
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor, edge_index: torch.Tensor, z: torch.Tensor) -> torch.Tensor:
h = x
for i, layer in enumerate(self.layers):
h = layer(h, edge_index, z)
if i < len(self.layers) - 1:
h = F.gelu(h)
h = self.dropout(h)
return h
# --- extracted class 24 ---
class RRF_Dataset(Dataset):
def __init__(self, strain, weights, seq_len=160): # Use seq_len=160 to match model input
self.seq_len = seq_len
self.strain = strain
self.weights = weights
print(f"Debug: RRF_Dataset __init__ - len(strain): {len(strain)}, seq_len: {self.seq_len}") # Debug print
# Calculate n only if strain is long enough
if len(strain) >= seq_len:
self.n = len(strain) // seq_len
else:
self.n = 0 # Set n to 0 if strain is too short
print(f"Debug: RRF_Dataset __init__ - Calculated self.n: {self.n}") # New debug print
# Add a check to ensure there's at least one sequence
if self.n == 0:
raise ValueError(f"Strain data length ({len(strain)}) is less than sequence length ({seq_len}). Cannot create any samples.")
def __len__(self):
return self.n
def __getitem__(self, idx):
start = idx * self.seq_len
# Extract the strain sequence x
x = self.strain[start:start+self.seq_len] # Shape: [seq_len]
# Use the mean of the provided weights as the global resonance factor w
w = np.mean(self.weights) # global resonance factor
# Define the target label y as the mean of the strain sequence x, scaled by w
# This creates a regression target derived from the strain data.
y = np.mean(x) * w # synthetic label (proxy resonance)
# Convert x and y to PyTorch tensors with float dtype
# The model expects input x as [1, seq_len] for a single sample, so add unsqueeze(0)
return torch.tensor(x).float().unsqueeze(0), torch.tensor(y).float()
def load_model_state(path, model_instance, map_location='cpu'):
'''Helper: load state_dict from path into model_instance (PyTorch).'''
state = torch.load(path, map_location=map_location)
if isinstance(state, dict) and ('state_dict' in state and isinstance(state['state_dict'], dict)):
state = state['state_dict']
model_instance.load_state_dict(state)
return model_instance