{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Import required libraries\nimport os\nimport timm\nimport torch\nimport pandas as pd\nfrom PIL import Image\nfrom torch import nn, optim\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\n\n# Define training configuration\nBATCH_SIZE = 8\nNUM_EPOCHS = 20\nLEARNING_RATE = 2.5e-5\nMODEL_NAME = \"efficientnet_b0\"\nIMAGE_WIDTH = 500\nIMAGE_HEIGHT = 250\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Define target order\nTARGET_NAMES = [\n    \"Dry_Clover_g\",\n    \"Dry_Dead_g\",\n    \"Dry_Green_g\",\n    \"GDM_g\",\n    \"Dry_Total_g\"\n]\n\n# Define data augmentation and preprocessing transforms\ndef get_transforms():\n    # Define train transform\n    train_transform = transforms.Compose([\n        transforms.Resize((IMAGE_HEIGHT, IMAGE_WIDTH)),\n        transforms.RandomHorizontalFlip(),\n        transforms.RandomVerticalFlip(),\n        transforms.RandomRotation(10),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406],\n                             [0.229, 0.224, 0.225])\n    ])\n\n    # Define validation transform\n    val_transform = transforms.Compose([\n        transforms.Resize((IMAGE_HEIGHT, IMAGE_WIDTH)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406],\n                             [0.229, 0.224, 0.225])\n    ])\n\n    return train_transform, val_transform\n\n\n# Define custom dataset for multi-target regression\nclass BiomassDataset(Dataset):\n    def __init__(self, df, base_dir, transform=None):\n        # Save dataframe\n        self.df = df\n\n        # Save base directory\n        self.base_dir = base_dir\n\n        # Save transform\n        self.transform = transform\n\n        # Build image to targets mapping\n        self.image_ids = df[\"image_path\"].unique().tolist()\n\n        # Precompute dictionary\n        self.img_to_targets = self._build_img_to_targets(df)\n\n    def _build_img_to_targets(self, df):\n        # Initialize dictionary\n        mapping = {}\n\n        # Group by image_path\n        grouped = df.groupby(\"image_path\")\n\n        # Iterate groups\n        for img_path, group in grouped:\n            # Initialize target vector\n            target_vec = []\n\n            # Fill targets in fixed order\n            for t in TARGET_NAMES:\n                # Filter row\n                row = group[group[\"target_name\"] == t]\n\n                # Get value or 0.0\n                if len(row) == 1:\n                    value = float(row[\"target\"].values[0])\n                else:\n                    value = 0.0\n\n                # Append value\n                target_vec.append(value)\n\n            # Save in mapping\n            mapping[img_path] = torch.tensor(target_vec, dtype=torch.float32)\n\n        return mapping\n\n    def __len__(self):\n        # Return number of unique images\n        return len(self.image_ids)\n\n    def __getitem__(self, idx):\n        # Get image path (relative)\n        rel_path = self.image_ids[idx]\n\n        # Build full path\n        img_path = os.path.join(self.base_dir, rel_path)\n\n        # Open image\n        image = Image.open(img_path).convert(\"RGB\")\n\n        # Apply transform\n        if self.transform:\n            image = self.transform(image)\n\n        # Get target vector\n        targets = self.img_to_targets[rel_path]\n\n        return image, targets\n\n# Define model creation function\ndef create_model(num_outputs):\n    # Create pretrained model\n    model = timm.create_model(MODEL_NAME, pretrained=False)\n\n    # Get number of features\n    in_features = model.get_classifier().in_features\n\n    # Replace classifier with regression head\n    model.classifier = nn.Linear(in_features, num_outputs)\n\n    # Move model to device\n    model = model.to(DEVICE)\n    return model\n\n# Define training function\ndef train_one_epoch(model, dataloader, criterion, optimizer):\n    # Set model to train\n    model.train()\n    running_loss = 0.0\n    total = 0\n\n    # Iterate dataloader\n    for images, targets in dataloader:\n        # Move to device\n        images = images.to(DEVICE)\n        targets = targets.to(DEVICE)\n\n        # Forward pass\n        outputs = model(images)\n\n        # Compute loss\n        loss = criterion(outputs, targets)\n\n        # Backward\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        # Accumulate loss\n        batch_size = images.size(0)\n        running_loss += loss.item() * batch_size\n        total += batch_size\n\n    # Compute epoch loss\n    epoch_loss = running_loss / total\n    return epoch_loss\n\n# Define validation function\ndef validate(model, dataloader, criterion):\n    # Set model to eval\n    model.eval()\n    running_loss = 0.0\n    total = 0\n\n    # Disable grad\n    with torch.no_grad():\n        for images, targets in dataloader:\n            # Move to device\n            images = images.to(DEVICE)\n            targets = targets.to(DEVICE)\n\n            # Forward\n            outputs = model(images)\n\n            # Loss\n            loss = criterion(outputs, targets)\n\n            # Accumulate\n            batch_size = images.size(0)\n            running_loss += loss.item() * batch_size\n            total += batch_size\n\n    # Compute\n    epoch_loss = running_loss / total\n    return epoch_loss\n\n# Define inference function\ndef run_inference(model, test_df, base_dir, transform, output_path):\n    # Get unique images\n    unique_images = test_df[\"image_path\"].unique().tolist()\n\n    # Create list for predictions\n    img_to_pred = {}\n\n    # Set model to eval\n    model.eval()\n\n    # Disable grad\n    with torch.no_grad():\n        # Iterate images\n        for img_rel in unique_images:\n            # Build path\n            img_path = os.path.join(base_dir, img_rel)\n\n            # Open image\n            image = Image.open(img_path).convert(\"RGB\")\n\n            # Apply transform\n            image = transform(image)\n\n            # Add batch dim\n            image = image.unsqueeze(0).to(DEVICE)\n\n            # Forward\n            outputs = model(image)\n\n            # Move to cpu\n            outputs = outputs.squeeze(0).cpu().numpy()\n\n            # Save\n            img_to_pred[img_rel] = outputs\n\n    # Build submission rows\n    rows = []\n\n    # Iterate test df rows\n    for _, row in test_df.iterrows():\n        # Get sample_id\n        sample_id = row[\"sample_id\"]\n\n        # Get image path\n        img_rel = row[\"image_path\"]\n\n        # Get target name\n        target_name = row[\"target_name\"]\n\n        # Get predictions array\n        preds = img_to_pred[img_rel]\n\n        # Get index\n        idx = TARGET_NAMES.index(target_name)\n\n        # Get value\n        value = float(preds[idx])\n\n        # Append row\n        rows.append({\n            \"sample_id\": sample_id,\n            \"target\": value\n        })\n\n    # Create dataframe\n    submission = pd.DataFrame(rows)\n\n    # Save\n    submission.to_csv(output_path, index=False)\n    print(f\"Submission saved to {output_path}\")\n\n# Define main\ndef main():\n    # Define data directory\n    DATA_DIR = \"/kaggle/input/csiro-biomass\"\n\n    # Define csv paths\n    TRAIN_CSV = os.path.join(DATA_DIR, \"train.csv\")\n    TEST_CSV = os.path.join(DATA_DIR, \"test.csv\")\n\n    # Read train csv\n    train_df = pd.read_csv(TRAIN_CSV)\n\n    # Get transforms\n    train_transform, val_transform = get_transforms()\n\n    # Get unique image paths\n    unique_imgs = train_df[\"image_path\"].unique()\n\n    # Create train and validation split on images\n    train_imgs, val_imgs = train_test_split(\n        unique_imgs,\n        test_size=0.2,\n        random_state=42\n    )\n\n    # Filter dataframes\n    train_df_split = train_df[train_df[\"image_path\"].isin(train_imgs)].reset_index(drop=True)\n    val_df_split = train_df[train_df[\"image_path\"].isin(val_imgs)].reset_index(drop=True)\n\n    # Create datasets\n    train_dataset = BiomassDataset(train_df_split, DATA_DIR, transform=train_transform)\n    val_dataset = BiomassDataset(val_df_split, DATA_DIR, transform=val_transform)\n\n    # Create dataloaders\n    train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\n    val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\n    # Create model\n    model = create_model(num_outputs=len(TARGET_NAMES))\n\n    # Define loss\n    criterion = nn.MSELoss()\n\n    # Define optimizer\n    optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)\n\n    # Train\n    for epoch in range(NUM_EPOCHS):\n        # Train epoch\n        train_loss = train_one_epoch(model, train_loader, criterion, optimizer)\n\n        # Validate\n        val_loss = validate(model, val_loader, criterion)\n\n        # Print\n        print(f\"Epoch [{epoch + 1}/{NUM_EPOCHS}]\")\n        print(f\"Train Loss: {train_loss:.4f}\")\n        print(f\"Val Loss:   {val_loss:.4f}\\n\")\n\n    # Read test csv\n    test_df = pd.read_csv(TEST_CSV)\n\n    # Run inference\n    run_inference(model, test_df, DATA_DIR, val_transform, \"/kaggle/working/submission.csv\")\n\n# Run main\nif __name__ == \"__main__\":\n    main()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null}]}