DEBUG:rasterio._base:Nodata success: 1, Nodata value: -32768.000000
DEBUG:rasterio._base:Dataset <open DatasetReader name='/Volumes/Crucial/xview3_dataset/full_dataset/full/validation/335f9a411884e9cbv/VV_dB.tif' mode='r'> is started.
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x317a72640>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x317a72670> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x317a72670>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x317a72640>
DEBUG:rasterio.env:Entering env context: <rasterio.env.Env object at 0x3259fe400>
DEBUG:rasterio.env:Starting outermost env
DEBUG:rasterio.env:No GDAL environment exists
DEBUG:rasterio.env:New GDAL environment <rasterio._env.GDALEnv object at 0x317a72670> created
DEBUG:rasterio._env:GDAL_DATA found in environment.
DEBUG:rasterio._env:PROJ_DATA found in environment.
DEBUG:rasterio._env:Started GDALEnv: self=<rasterio._env.GDALEnv object at 0x317a72670>.
DEBUG:rasterio.env:Entered env context: <rasterio.env.Env object at 0x3259fe400>
DEBUG:rasterio._io:Output nodata value read from file: -32768.0
DEBUG:rasterio._io:Output nodata values: [-32768.0]
DEBUG:rasterio._io:all_valid: False
DEBUG:rasterio._io:mask_flags: ([<MaskFlags.nodata: 8>],)
DEBUG:rasterio._io:Jump straight to _read()
DEBUG:rasterio._io:Window: Window(col_off=9793, row_off=9745, width=512, height=512)
DEBUG:rasterio._io:IO window xoff=9793.0 yoff=9745.0 width=512.0 height=512.0
DEBUG:rasterio.env:Exiting env context: <rasterio.env.Env object at 0x3259fe400>
DEBUG:rasterio.env:Cleared existing <rasterio._env.GDALEnv object at 0x317a72670> options
DEBUG:rasterio._env:Stopped GDALEnv <rasterio._env.GDALEnv object at 0x317a72670>.
DEBUG:rasterio.env:Exiting outermost env
DEBUG:rasterio.env:Exited env context: <rasterio.env.Env object at 0x3259fe400>
[DEBUG] Channel 0 shape: (512, 512)
[DEBUG] Channel 1 shape: (512, 512)
(289.0, 264.0, 0.0) [<class 'float'>, <class 'float'>, <class 'float'>]
Error executing job with overrides: ['+config=configs/multilabel_circle_net.yaml']
Traceback (most recent call last):
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 525, in <module>
    main()
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/main.py", line 94, in decorated_main
    _run_hydra(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 394, in _run_hydra
    _run_app(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 457, in _run_app
    run_and_report(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 223, in run_and_report
    raise ex
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 220, in run_and_report
    return func()
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/utils.py", line 458, in <lambda>
    lambda: hydra.run(
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/_internal/hydra.py", line 132, in run
    _ = ret.return_value
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/core/utils.py", line 260, in return_value
    raise self._return_value
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/hydra/core/utils.py", line 186, in run_job
    ret.return_value = task_function(task_cfg)
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 521, in main
    CenternetPipeline(cfg).train()
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 416, in train
    model = self.build_model(config, self.loaders)
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/train_multilabel.py", line 207, in build_model
    model = nas.search(config.nas.num_iterations, loaders['train'], loaders['valid'])
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 39, in search
    scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 39, in <listcomp>
    scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 120, in evaluate_model
    loss = self.calculate_loss(outputs, targets)
  File "/Users/samerkharboush/Downloads/SAR-MPS-OptunaNasFbnYoloTransformers/xview3/nas.py", line 137, in calculate_loss
    total_loss += F.l1_loss(outputs['size'], targets['CENTERNET_TARGET_SIZE'])
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/nn/functional.py", line 3353, in l1_loss
    expanded_input, expanded_target = torch.broadcast_tensors(input, target)
  File "/opt/anaconda3/envs/sam3/lib/python3.9/site-packages/torch/functional.py", line 77, in broadcast_tensors
    return _VF.broadcast_tensors(tensors)  # type: ignore[attr-defined]
RuntimeError: The size of tensor a (64) must match the size of tensor b (128) at non-singleton dimension 3
(sam3) samerkharboush@Samers-MBP SAR-MPS-OptunaNasFbnYoloTransformers % 

I think nas.py file doesn't match here'it's
import torch
import torch.nn as nn
import torch.nn.functional as F
import random
import numpy as np
from skopt import gp_minimize
from skopt.space import Real, Integer, Categorical
import math
import copy
from torch import Tensor
from torch.optim import Adam
from torch.utils.data import DataLoader
from xview3.utils import TorchDevice
from xview3.centernet.bboxer.multilabel_circle_coder import MultilabelCircleNetCoder

class ProgressiveNAS(nn.Module):
    def __init__(self, base_model, search_space):
        super(ProgressiveNAS, self).__init__()
        self.base_model = base_model
        self.search_space = search_space
        self.box_coder = MultilabelCircleNetCoder(
            image_size=(1024, 1024),
            output_stride=2,
            max_objects=512,
            heatmap_encoding="umich",
            labels_encoding="circle",
            ignore_value=-1
        )

        device = TorchDevice.choose_torch_device()
        self.population_size = 20
        self.tournament_size = 5
        self.output_channels = 256 

    def search(self, num_iterations, train_loader, val_loader):
        population = [self.mutate_model(self.base_model) for _ in range(self.population_size)]
        
        for _ in range(num_iterations):
            scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
            
            new_population = []
            for _ in range(self.population_size):
                parent1 = self.tournament_selection(population, scores)
                parent2 = self.tournament_selection(population, scores)
                child = self.crossover(parent1, parent2)
                child = self.mutate_model(child)
                new_population.append(child)
            
            population = new_population

        best_model = max(population, key=lambda model: self.evaluate_model(model, train_loader, val_loader))
        return best_model

    def tournament_selection(self, population, scores):
        tournament = random.sample(list(zip(population, scores)), self.tournament_size)
        return max(tournament, key=lambda x: x[1])[0]

    def crossover(self, parent1, parent2):
        child = copy.deepcopy(parent1)
        for name, param in child.named_parameters():
            if random.random() < 0.5:
                param.data = parent2.state_dict()[name].clone()
        return child

    def mutate_model(self, model):
        new_model = copy.deepcopy(model)
        for param in new_model.parameters():
            if random.random() < 0.1:
                param.data += torch.randn_like(param.data) * 0.1
        
        # Structure mutation
        if random.random() < 0.2:
            new_layer = nn.Linear(64, 64)  # Example layer, adjust as needed
            if hasattr(new_model, 'encoder') and hasattr(new_model.encoder, 'add_module'):
                new_model.encoder.add_module(f"extra_layer_{id(new_layer)}", new_layer)
            elif hasattr(new_model, 'decoder') and hasattr(new_model.decoder, 'add_module'):
                new_model.decoder.add_module(f"extra_layer_{id(new_layer)}", new_layer)
        
        return new_model

    def evaluate_model(self, model, train_loader, val_loader):
        device = TorchDevice.choose_torch_device()
        model.eval()
        total_loss = 0.0
        total_correct = 0
        total_samples = 0

        with torch.no_grad():
            for batch_idx, batch in enumerate(train_loader):
                # Extract inputs
                inputs = batch.get('INPUT_IMAGE_KEY')
                if inputs is None:
                    raise ValueError("Batch does not contain 'INPUT_IMAGE_KEY'. Available keys: {}".format(list(batch.keys())))
                
                # Extract targets
                targets = {
                    'CENTERNET_TARGET_OBJECTNESS_MAP': batch.get('CENTERNET_TARGET_OBJECTNESS_MAP'),
                    'CENTERNET_TARGET_VESSEL_MAP': batch.get('CENTERNET_TARGET_VESSEL_MAP'),
                    'CENTERNET_TARGET_FISHING_MAP': batch.get('CENTERNET_TARGET_FISHING_MAP'),
                    'CENTERNET_TARGET_SIZE': batch.get('CENTERNET_TARGET_SIZE'),
                    'CENTERNET_TARGET_OFFSET': batch.get('CENTERNET_TARGET_OFFSET'),
                }
                # Ensure all targets are present
                if None in targets.values():
                    missing_keys = [k for k, v in targets.items() if v is None]
                    raise ValueError(f"Batch missing target keys: {missing_keys}")
                
                # Move data to device
                device = next(model.parameters()).device
                inputs = inputs.to(device)
                targets = {k: v.to(device) for k, v in targets.items()}

                # Proceed with evaluation
                outputs = model(inputs)
                required_keys = ['heatmap', 'size', 'offset', 'vessel_map', 'fishing_map']
                for key in required_keys:
                    if key not in outputs:
                        raise KeyError(f"Model output missing required key: '{key}'")
                
                loss = self.calculate_loss(outputs, targets)

                # Update metrics
                total_loss += loss.item() * inputs.size(0)
                total_samples += inputs.size(0)
                
                # For testing, break after first batch
                break

        # Return evaluation score (e.g., total loss)
        average_loss = total_loss / total_samples
        return -average_loss  # Negative loss if you're maximizing the evaluation score

    
    def calculate_loss(self, outputs, targets):
        total_loss = 0
        total_loss += self.focal_loss(outputs['heatmap'], targets['CENTERNET_TARGET_OBJECTNESS_MAP'])
        total_loss += F.l1_loss(outputs['size'], targets['CENTERNET_TARGET_SIZE'])
        total_loss += F.l1_loss(outputs['offset'], targets['CENTERNET_TARGET_OFFSET'])
        total_loss += F.binary_cross_entropy_with_logits(outputs['vessel'], targets['CENTERNET_TARGET_VESSEL_MAP'])
        total_loss += F.binary_cross_entropy_with_logits(outputs['fishing'], targets['CENTERNET_TARGET_FISHING_MAP'])
        return total_loss


    def focal_loss(self, pred, gt):
        pos_inds = gt.eq(1).float()
        neg_inds = gt.lt(1).float()
        
        # Ensure pred and gt have the same size
        if pred.size() != gt.size():
            pred = F.interpolate(pred, size=gt.size()[2:], mode='bilinear', align_corners=False)
        
        neg_weights = torch.pow(1 - gt, 4)
        pos_loss = -torch.log(pred) * torch.pow(1 - pred, 2) * pos_inds
        neg_loss = -torch.log(1 - pred) * torch.pow(pred, 2) * neg_weights * neg_inds
        
        num_pos = pos_inds.float().sum()
        pos_loss = pos_loss.sum()
        neg_loss = neg_loss.sum()

        if num_pos == 0:
            loss = neg_loss
        else:
            loss = (pos_loss + neg_loss) / num_pos
        return loss


class NAS(nn.Module):
    def __init__(self, search_space, evaluation_function):
        super(NAS, self).__init__()
        self.search_space = search_space
        self.evaluation_function = evaluation_function
        self.layers = nn.ModuleList()
        self.output_channels = 256

    def build_architecture(self):
        architecture = self.sample_architecture()
        num_layers = int(architecture.get('num_layers', 2))
        num_filters = int(architecture.get('num_filters', 64))
        kernel_size = int(architecture.get('kernel_size', 3))
        activation = architecture.get('activation', 'relu')

        input_channels = 256  # Assuming neck_features has 256 channels

        for i in range(num_layers):
            conv = nn.Conv2d(input_channels, num_filters, kernel_size, padding=kernel_size // 2, bias=False)
            bn = nn.BatchNorm2d(num_filters)
            act = nn.ReLU(inplace=True) if activation == 'relu' else nn.LeakyReLU(0.1, inplace=True)
            self.layers.append(nn.Sequential(conv, bn, act))
            input_channels = num_filters

        self.output_channels = num_filters

    def sample_architecture(self):
        # For simplicity, we'll randomly sample from the search space
        architecture = {}
        for key, value_range in self.search_space.items():
            if isinstance(value_range, list):
                architecture[key] = random.choice(value_range)
            elif isinstance(value_range, tuple):
                architecture[key] = random.randint(*value_range)
        return architecture

    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return x

    def search(self, num_iterations, train_loader, val_loader):
        population = self.initialize_population()
        
        for _ in range(num_iterations):
            scores = [self.evaluate_model(model, train_loader, val_loader) for model in population]
            best_model = population[np.argmax(scores)]
            
            new_population = [best_model]
            while len(new_population) < len(population):
                parent1, parent2 = self.selection(population, scores)
                child = self.crossover(parent1, parent2)
                child = self.mutate(child)
                new_population.append(child)
            
            population = new_population
        
        return population[np.argmax([self.evaluate_model(model, train_loader, val_loader) for model in population])]

    def apply_to_model(self, model, architecture):
        for name, module in model.named_modules():
            if isinstance(module, nn.Conv2d):
                if 'num_filters' in architecture:
                    in_channels = module.in_channels
                    out_channels = architecture['num_filters']
                    kernel_size = architecture.get('kernel_size', module.kernel_size)
                    new_conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=kernel_size[0]//2)
                    setattr(model, name, new_conv)
            elif isinstance(module, nn.ReLU):
                if architecture.get('activation') == 'leaky_relu':
                    setattr(model, name, nn.LeakyReLU())
        
        if 'num_layers' in architecture:
            current_layers = len(list(model.children()))
            if architecture['num_layers'] > current_layers:
                for _ in range(architecture['num_layers'] - current_layers):
                    model.add_module(f'extra_layer_{_}', nn.Linear(64, 64))  # Adjust as needed
            elif architecture['num_layers'] < current_layers:
                for _ in range(current_layers - architecture['num_layers']):
                    model = nn.Sequential(*list(model.children())[:-1])
        
        return model

    def calculate_loss(self, outputs, targets):
        # Assuming outputs and targets are dictionaries with matching keys
        # You should define the loss functions for each output-target pair
        total_loss = 0

        # Print the keys and values of the outputs dictionary for debugging
        print("Outputs keys:", outputs.keys())
        print("Targets keys:", targets.keys())

        # Heatmap loss (e.g., Focal Loss)
        heatmap_loss = self.focal_loss(outputs['heatmap'], targets['CENTERNET_TARGET_OBJECTNESS_MAP'])
        total_loss += heatmap_loss

        # Size loss (e.g., L1 Loss)
        size_loss = F.l1_loss(outputs['size'], targets['CENTERNET_TARGET_SIZE'])
        total_loss += size_loss

        # Offset loss (e.g., L1 Loss)
        offset_loss = F.l1_loss(outputs['offset'], targets['CENTERNET_TARGET_OFFSET'])
        total_loss += offset_loss

        return total_loss

    def focal_loss(self, pred, gt, alpha=2, beta=4, eps=1e-6):
        pos_inds = gt.eq(1).float()
        neg_inds = gt.lt(1).float()
        neg_weights = torch.pow(1 - gt, beta)

        # Ensure pred and gt have the same spatial dimensions
        if pred.size() != gt.size():
            raise ValueError(f"pred and gt must have the same size, but got pred: {pred.size()} and gt: {gt.size()}")

        pred = torch.clamp(pred, eps, 1 - eps)
        pos_loss = -torch.log(pred) * torch.pow(1 - pred, alpha) * pos_inds
        neg_loss = -torch.log(1 - pred) * torch.pow(pred, alpha) * neg_weights * neg_inds

        num_pos = pos_inds.float().sum()
        loss = (pos_loss + neg_loss).sum() / num_pos.clamp(min=1)
        return loss

base_model = nn.Sequential(
    nn.Conv2d(3, 64, 3, padding=1),
    nn.ReLU(),
    nn.Conv2d(64, 64, 3, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(2)
)

search_space = {
    'num_layers': (2, 5),
    'num_filters': (32, 128),
    'kernel_size': (3, 5),
    'activation': ['relu', 'leaky_relu']
}