{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","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 torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models\nimport pandas as pd\nimport numpy as np\nimport cv2\nfrom sklearn.preprocessing import StandardScaler\nimport warnings\nimport os\nwarnings.filterwarnings('ignore')\n\n# Konfiguracija\nCONFIG = {\n    'img_size': (250, 500),\n    'batch_size': 16,\n    'epochs': 200,\n    'learning_rate': 5e-2,\n    'validation_split': 0.2,\n    'random_state': 42,\n    'device': 'cuda' if torch.cuda.is_available() else 'cpu'\n}\n\n# Mapiranje target imena\nTARGET_MAPPING = {\n    'Dry_Green_g': 'Dry_green_vegetation',\n    'Dry_Dead_g': 'Dry_dead_material', \n    'Dry_Clover_g': 'Dry_clover_biomass',\n    'GDM_g': 'Green_dry_matter',\n    'Dry_Total_g': 'Total_dry_biomass'\n}\n\nREVERSE_TARGET_MAPPING = {v: k for k, v in TARGET_MAPPING.items()}\n\n# DATA PATH\ndata_path = '/kaggle/input/csiro-biomass/'\n\n# =============================================================================\n# 1. MODELI BEZ INTERNET KONEKCIJE\n# =============================================================================\n\ndef create_model_without_pretrained():\n    \"\"\"Kreira modele bez pre-trained težina za Kaggle bez interneta\"\"\"\n    \n    class BiomassSpecialist(nn.Module):\n        def __init__(self, num_biomass_targets=3):\n            super().__init__()\n            # Koristimo architecture bez pre-trained weights\n            self.backbone = models.efficientnet_b4(pretrained=False)\n            self.backbone.classifier = nn.Identity()\n            \n            # Biomass-specific branch\n            self.biomass_head = nn.Sequential(\n                nn.Linear(1792, 512),\n                nn.ReLU(),\n                nn.Dropout(0.3),\n                nn.Linear(512, 256),\n                nn.ReLU(),\n                nn.Linear(256, num_biomass_targets)\n            )\n            \n        def forward(self, x):\n            features = self.backbone(x)\n            biomass_outputs = self.biomass_head(features)\n            return biomass_outputs\n\n    class DryMaterialSpecialist(nn.Module):\n        def __init__(self, num_dry_targets=2):\n            super().__init__()\n            self.backbone = models.resnet50(pretrained=False)\n            self.backbone.fc = nn.Identity()\n            \n            self.dry_head = nn.Sequential(\n                nn.Linear(2048, 512),\n                nn.ReLU(),\n                nn.Dropout(0.4),\n                nn.Linear(512, 256),\n                nn.ReLU(),\n                nn.Linear(256, num_dry_targets)\n            )\n            \n        def forward(self, x):\n            features = self.backbone(x)\n            dry_outputs = self.dry_head(features)\n            return dry_outputs\n\n    class GlobalPredictor(nn.Module):\n        def __init__(self, num_targets=5):\n            super().__init__()\n            self.backbone = models.efficientnet_b4(pretrained=False)\n            self.backbone.classifier = nn.Identity()\n            \n            self.shared_layers = nn.Sequential(\n                nn.Linear(1792, 512),\n                nn.ReLU(),\n                nn.Dropout(0.3),\n                nn.Linear(512, 256),\n                nn.ReLU()\n            )\n            \n            self.output_layers = nn.ModuleList([\n                nn.Linear(256, 1) for _ in range(num_targets)\n            ])\n            \n        def forward(self, x):\n            features = self.backbone(x)\n            shared_features = self.shared_layers(features)\n            \n            outputs = []\n            for layer in self.output_layers:\n                outputs.append(layer(shared_features))\n            \n            return torch.cat(outputs, dim=1)\n\n    class CloverClassifier(nn.Module):\n        def __init__(self):\n            super().__init__()\n            self.backbone = models.efficientnet_b2(pretrained=False)\n            self.backbone.classifier = nn.Identity()\n            \n            self.classifier = nn.Sequential(\n                nn.Linear(1408, 512),\n                nn.ReLU(),\n                nn.Dropout(0.3),\n                nn.Linear(512, 256),\n                nn.ReLU(),\n                nn.Linear(256, 1),\n                nn.Sigmoid()\n            )\n            \n        def forward(self, x):\n            features = self.backbone(x)\n            clover_prob = self.classifier(features)\n            return clover_prob\n\n    return BiomassSpecialist, DryMaterialSpecialist, GlobalPredictor, CloverClassifier\n\n# Kreiraj model klase\nBiomassSpecialist, DryMaterialSpecialist, GlobalPredictor, CloverClassifier = create_model_without_pretrained()\n\n# =============================================================================\n# 2. CUSTOM DATASET KLASE\n# =============================================================================\n\nclass BiomassDataset(Dataset):\n    def __init__(self, images, targets=None, is_train=True, has_clover=None):\n        self.images = images\n        self.targets = targets\n        self.is_train = is_train\n        self.has_clover = has_clover\n        \n    def __len__(self):\n        return len(self.images)\n    \n    def __getitem__(self, idx):\n        image = self.images[idx]\n        image = torch.tensor(image, dtype=torch.float32).permute(2, 0, 1)  # HWC to CHW\n        \n        if self.is_train and self.targets is not None:\n            target_dict = {}\n            for target_name in self.targets.keys():\n                target_dict[target_name] = torch.tensor(\n                    self.targets[target_name][idx], dtype=torch.float32\n                )\n            \n            if self.has_clover is not None:\n                target_dict['has_clover'] = torch.tensor(\n                    self.has_clover[idx], dtype=torch.float32\n                )\n            \n            return image, target_dict\n        else:\n            return image\n\n# =============================================================================\n# 3. DATA LOADER\n# =============================================================================\n\nclass BiomassDataLoader:\n    def __init__(self, config):\n        self.config = config\n        self.target_scalers = {}\n        \n    def load_train_data(self, train_csv_path, train_image_dir):\n        df = pd.read_csv(train_csv_path)\n        \n        image_groups = df.groupby('image_path')\n        \n        images = []\n        targets_dict = {\n            'Dry_green_vegetation': [],\n            'Dry_dead_material': [],\n            'Dry_clover_biomass': [],\n            'Green_dry_matter': [],\n            'Total_dry_biomass': []\n        }\n        \n        has_clover = []\n        \n        for image_path, group in image_groups:\n            full_img_path = os.path.join(train_image_dir, image_path)\n            img = self.load_and_preprocess_image(full_img_path)\n            if img is not None:\n                images.append(img)\n                \n                target_row = {target: 0.0 for target in targets_dict.keys()}\n                clover_present = False\n                \n                for _, row in group.iterrows():\n                    target_name = TARGET_MAPPING[row['target_name']]\n                    target_row[target_name] = row['target']\n                    \n                    if target_name == 'Dry_clover_biomass' and row['target'] > 0:\n                        clover_present = True\n                \n                for target_name in targets_dict.keys():\n                    targets_dict[target_name].append(target_row[target_name])\n                \n                has_clover.append(1.0 if clover_present else 0.0)\n        \n        images = np.array(images)\n        targets = {k: np.array(v) for k, v in targets_dict.items()}\n        \n        return images, targets, has_clover\n    \n    def load_test_data(self, test_csv_path, test_image_dir):\n        df = pd.read_csv(test_csv_path)\n        \n        image_groups = df.groupby('image_path')\n        \n        test_images = []\n        test_image_paths = []\n        test_target_names = []\n        \n        for image_path, group in image_groups:\n            full_img_path = os.path.join(test_image_dir, image_path)\n            img = self.load_and_preprocess_image(full_img_path)\n            if img is not None:\n                test_images.append(img)\n                test_image_paths.append(image_path)\n                \n                target_names = [TARGET_MAPPING[row['target_name']] for _, row in group.iterrows()]\n                test_target_names.append(target_names)\n        \n        return np.array(test_images), test_image_paths, test_target_names\n    \n    def load_and_preprocess_image(self, image_path):\n        try:\n            img = cv2.imread(image_path)\n            if img is None:\n                print(f\"Warning: Could not load image {image_path}\")\n                return None\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            img = cv2.resize(img, self.config['img_size'])\n            img = img.astype('float32') / 255.0\n            return img\n        except Exception as e:\n            print(f\"Error loading image {image_path}: {e}\")\n            return None\n    \n    def scale_targets(self, targets, fit=True):\n        scaled_targets = {}\n        \n        for target_name, values in targets.items():\n            if fit:\n                scaler = StandardScaler()\n                scaled_values = scaler.fit_transform(values.reshape(-1, 1)).flatten()\n                self.target_scalers[target_name] = scaler\n            else:\n                scaler = self.target_scalers[target_name]\n                scaled_values = scaler.transform(values.reshape(-1, 1)).flatten()\n            \n            scaled_targets[target_name] = scaled_values\n        \n        return scaled_targets\n    \n    def inverse_scale_targets(self, predictions):\n        unscaled_predictions = {}\n        \n        for target_name, values in predictions.items():\n            if target_name in self.target_scalers:\n                scaler = self.target_scalers[target_name]\n                if isinstance(values, torch.Tensor):\n                    values_np = values.cpu().numpy()\n                else:\n                    values_np = np.array(values)\n                \n                if values_np.ndim == 1:\n                    values_np = values_np.reshape(-1, 1)\n                \n                unscaled_values = scaler.inverse_transform(values_np).flatten()\n                unscaled_predictions[target_name] = unscaled_values\n            else:\n                unscaled_predictions[target_name] = values\n        \n        return unscaled_predictions\n\n# =============================================================================\n# 4. ENSEMBLE KLASA\n# =============================================================================\n\nclass PyTorchBiomassEnsemble:\n    def __init__(self, config):\n        self.config = config\n        self.models = {}\n        self.data_loader = BiomassDataLoader(config)\n        self.device = config['device']\n        self.clover_threshold = 0.5\n        \n    def build_models(self):\n        \"\"\"Gradi sve modele u ensemble-u\"\"\"\n        print(\"Building models without pretrained weights...\")\n        self.models = {\n            'biomass_specialist': BiomassSpecialist().to(self.device),\n            'dry_material_specialist': DryMaterialSpecialist().to(self.device),\n            'global_predictor': GlobalPredictor().to(self.device),\n            'clover_classifier': CloverClassifier().to(self.device)\n        }\n        print(\"Models built successfully!\")\n        \n    def train_model(self, model, train_loader, val_loader, model_type):\n        \"\"\"Trenira jedan model\"\"\"\n        optimizer = optim.Adam(model.parameters(), lr=self.config['learning_rate'])\n        \n        if model_type == 'clover_classifier':\n            criterion = nn.BCELoss()\n        else:\n            criterion = nn.MSELoss()\n        \n        best_val_loss = float('inf')\n        patience = 55  # Povećan patience zbog training from scratch\n        patience_counter = 0\n        \n        for epoch in range(self.config['epochs']):\n            # Training\n            model.train()\n            train_loss = 0.0\n            \n            for batch_images, batch_targets in train_loader:\n                batch_images = batch_images.to(self.device)\n                \n                optimizer.zero_grad()\n                \n                if model_type == 'biomass_specialist':\n                    biomass_targets = torch.stack([\n                        batch_targets['Dry_green_vegetation'],\n                        batch_targets['Dry_clover_biomass'],\n                        batch_targets['Green_dry_matter']\n                    ], dim=1).to(self.device)\n                    \n                    outputs = model(batch_images)\n                    loss = criterion(outputs, biomass_targets)\n                    \n                elif model_type == 'dry_material_specialist':\n                    dry_targets = torch.stack([\n                        batch_targets['Dry_dead_material'],\n                        batch_targets['Total_dry_biomass']\n                    ], dim=1).to(self.device)\n                    \n                    outputs = model(batch_images)\n                    loss = criterion(outputs, dry_targets)\n                    \n                elif model_type == 'clover_classifier':\n                    clover_targets = batch_targets['has_clover'].to(self.device)\n                    outputs = model(batch_images)\n                    loss = criterion(outputs.squeeze(), clover_targets)\n                    \n                else:  # global_predictor\n                    global_targets = torch.stack([\n                        batch_targets['Dry_green_vegetation'],\n                        batch_targets['Dry_dead_material'],\n                        batch_targets['Dry_clover_biomass'],\n                        batch_targets['Green_dry_matter'],\n                        batch_targets['Total_dry_biomass']\n                    ], dim=1).to(self.device)\n                    \n                    outputs = model(batch_images)\n                    loss = criterion(outputs, global_targets)\n                \n                loss.backward()\n                optimizer.step()\n                train_loss += loss.item()\n            \n            # Validation\n            model.eval()\n            val_loss = 0.0\n            \n            with torch.no_grad():\n                for batch_images, batch_targets in val_loader:\n                    batch_images = batch_images.to(self.device)\n                    \n                    if model_type == 'biomass_specialist':\n                        biomass_targets = torch.stack([\n                            batch_targets['Dry_green_vegetation'],\n                            batch_targets['Dry_clover_biomass'],\n                            batch_targets['Green_dry_matter']\n                        ], dim=1).to(self.device)\n                        \n                        outputs = model(batch_images)\n                        loss = criterion(outputs, biomass_targets)\n                        \n                    elif model_type == 'dry_material_specialist':\n                        dry_targets = torch.stack([\n                            batch_targets['Dry_dead_material'],\n                            batch_targets['Total_dry_biomass']\n                        ], dim=1).to(self.device)\n                        \n                        outputs = model(batch_images)\n                        loss = criterion(outputs, dry_targets)\n                        \n                    elif model_type == 'clover_classifier':\n                        clover_targets = batch_targets['has_clover'].to(self.device)\n                        outputs = model(batch_images)\n                        loss = criterion(outputs.squeeze(), clover_targets)\n                        \n                    else:  # global_predictor\n                        global_targets = torch.stack([\n                            batch_targets['Dry_green_vegetation'],\n                            batch_targets['Dry_dead_material'],\n                            batch_targets['Dry_clover_biomass'],\n                            batch_targets['Green_dry_matter'],\n                            batch_targets['Total_dry_biomass']\n                        ], dim=1).to(self.device)\n                        \n                        outputs = model(batch_images)\n                        loss = criterion(outputs, global_targets)\n                    \n                    val_loss += loss.item()\n            \n            avg_train_loss = train_loss / len(train_loader)\n            avg_val_loss = val_loss / len(val_loader)\n            \n            if epoch % 10 == 0:\n                print(f'{model_type} - Epoch {epoch}: Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}')\n            \n            # Early stopping\n            if avg_val_loss < best_val_loss:\n                best_val_loss = avg_val_loss\n                patience_counter = 0\n                torch.save(model.state_dict(), f'best_{model_type}.pth')\n            else:\n                patience_counter += 1\n                if patience_counter >= patience:\n                    print(f'Early stopping for {model_type}')\n                    break\n        \n        # Load best model\n        model.load_state_dict(torch.load(f'best_{model_type}.pth'))\n        print(f'{model_type} training completed!')\n    \n    def train_ensemble(self, train_images, train_targets, has_clover):\n        \"\"\"Trenira ceo ensemble\"\"\"\n        # Podela na train/validation\n        num_samples = len(train_images)\n        indices = np.random.permutation(num_samples)\n        split_idx = int(num_samples * (1 - self.config['validation_split']))\n        \n        train_idx = indices[:split_idx]\n        val_idx = indices[split_idx:]\n        \n        # Create datasets\n        train_dataset = BiomassDataset(\n            train_images[train_idx], \n            {k: v[train_idx] for k, v in train_targets.items()},\n            has_clover=np.array(has_clover)[train_idx]\n        )\n        val_dataset = BiomassDataset(\n            train_images[val_idx], \n            {k: v[val_idx] for k, v in train_targets.items()},\n            has_clover=np.array(has_clover)[val_idx]\n        )\n        \n        train_loader = DataLoader(train_dataset, batch_size=self.config['batch_size'], shuffle=True)\n        val_loader = DataLoader(val_dataset, batch_size=self.config['batch_size'], shuffle=False)\n        \n        # Treniranje svakog modela\n        print(\"Treniranje Biomass Specialist modela...\")\n        self.train_model(self.models['biomass_specialist'], train_loader, val_loader, 'biomass_specialist')\n        \n        print(\"Treniranje Dry Material Specialist modela...\")\n        self.train_model(self.models['dry_material_specialist'], train_loader, val_loader, 'dry_material_specialist')\n        \n        print(\"Treniranje Global Predictor modela...\")\n        self.train_model(self.models['global_predictor'], train_loader, val_loader, 'global_predictor')\n        \n        print(\"Treniranje Clover Classifier modela...\")\n        self.train_model(self.models['clover_classifier'], train_loader, val_loader, 'clover_classifier')\n    \n    def predict(self, images):\n        \"\"\"Pravi predikcije koristeći ensemble\"\"\"\n        self.models['biomass_specialist'].eval()\n        self.models['dry_material_specialist'].eval()\n        self.models['global_predictor'].eval()\n        self.models['clover_classifier'].eval()\n        \n        test_dataset = BiomassDataset(images, is_train=False)\n        test_loader = DataLoader(test_dataset, batch_size=self.config['batch_size'], shuffle=False)\n        \n        all_predictions = {\n            'biomass_specialist': {'Dry_green_vegetation': [], 'Dry_clover_biomass': [], 'Green_dry_matter': []},\n            'dry_material_specialist': {'Dry_dead_material': [], 'Total_dry_biomass': []},\n            'global_predictor': {\n                'Dry_green_vegetation': [], 'Dry_dead_material': [], 'Dry_clover_biomass': [],\n                'Green_dry_matter': [], 'Total_dry_biomass': []\n            }\n        }\n        \n        clover_predictions = []\n        \n        with torch.no_grad():\n            for batch_images in test_loader:\n                batch_images = batch_images.to(self.device)\n                \n                # Biomass specialist\n                biomass_preds = self.models['biomass_specialist'](batch_images)\n                all_predictions['biomass_specialist']['Dry_green_vegetation'].extend(\n                    biomass_preds[:, 0].cpu().numpy()\n                )\n                all_predictions['biomass_specialist']['Dry_clover_biomass'].extend(\n                    biomass_preds[:, 1].cpu().numpy()\n                )\n                all_predictions['biomass_specialist']['Green_dry_matter'].extend(\n                    biomass_preds[:, 2].cpu().numpy()\n                )\n                \n                # Dry material specialist\n                dry_preds = self.models['dry_material_specialist'](batch_images)\n                all_predictions['dry_material_specialist']['Dry_dead_material'].extend(\n                    dry_preds[:, 0].cpu().numpy()\n                )\n                all_predictions['dry_material_specialist']['Total_dry_biomass'].extend(\n                    dry_preds[:, 1].cpu().numpy()\n                )\n                \n                # Global predictor\n                global_preds = self.models['global_predictor'](batch_images)\n                all_predictions['global_predictor']['Dry_green_vegetation'].extend(\n                    global_preds[:, 0].cpu().numpy()\n                )\n                all_predictions['global_predictor']['Dry_dead_material'].extend(\n                    global_preds[:, 1].cpu().numpy()\n                )\n                all_predictions['global_predictor']['Dry_clover_biomass'].extend(\n                    global_preds[:, 2].cpu().numpy()\n                )\n                all_predictions['global_predictor']['Green_dry_matter'].extend(\n                    global_preds[:, 3].cpu().numpy()\n                )\n                all_predictions['global_predictor']['Total_dry_biomass'].extend(\n                    global_preds[:, 4].cpu().numpy()\n                )\n                \n                # Clover classifier\n                clover_probs = self.models['clover_classifier'](batch_images)\n                clover_probs_np = clover_probs.cpu().numpy()\n                \n                if clover_probs_np.ndim == 0:\n                    clover_predictions.append(float(clover_probs_np))\n                else:\n                    clover_predictions.extend(clover_probs_np.flatten().tolist())\n        \n        # Kombinacija predikcija\n        final_predictions = {}\n        target_names = [\n            'Dry_green_vegetation', 'Dry_dead_material', \n            'Dry_clover_biomass', 'Green_dry_matter', 'Total_dry_biomass'\n        ]\n        \n        # Prilagodi na osnovu clover klasifikacije\n        for i, clover_prob in enumerate(clover_predictions):\n            has_clover = clover_prob > self.clover_threshold\n            if not has_clover:\n                for model_name in ['biomass_specialist', 'global_predictor']:\n                    if 'Dry_clover_biomass' in all_predictions[model_name]:\n                        all_predictions[model_name]['Dry_clover_biomass'][i] = 0.0\n        \n        weights = {\n            'Dry_green_vegetation': {'biomass_specialist': 0.4, 'global_predictor': 0.6},\n            'Dry_dead_material': {'dry_material_specialist': 0.5, 'global_predictor': 0.5},\n            'Dry_clover_biomass': {'biomass_specialist': 0.4, 'global_predictor': 0.6},\n            'Green_dry_matter': {'biomass_specialist': 0.4, 'global_predictor': 0.6},\n            'Total_dry_biomass': {'dry_material_specialist': 0.4, 'global_predictor': 0.6}\n        }\n        \n        for target in target_names:\n            weighted_sum = 0\n            total_weight = 0\n            \n            for model_name, weight in weights[target].items():\n                if target in all_predictions[model_name]:\n                    prediction = np.array(all_predictions[model_name][target])\n                    weighted_sum += weight * prediction\n                    total_weight += weight\n            \n            if total_weight > 0:\n                final_predictions[target] = weighted_sum / total_weight\n            else:\n                final_predictions[target] = all_predictions['global_predictor'][target]\n        \n        return final_predictions, clover_predictions\n\n# =============================================================================\n# 5. GLAVNI KOD\n# =============================================================================\n\ndef main():\n    print(\"Pokretanje PyTorch Biomass Prediction Modela (No Internet)...\")\n    print(f\"Koristi se device: {CONFIG['device']}\")\n    \n    # Inicijalizacija\n    ensemble = PyTorchBiomassEnsemble(CONFIG)\n    data_loader = BiomassDataLoader(CONFIG)\n    \n    # Učitavanje trening podataka\n    print(\"Učitavanje trening podataka...\")\n    train_csv_path = os.path.join(data_path, 'train.csv')\n    train_image_dir = data_path\n    \n    train_images, train_targets, has_clover = data_loader.load_train_data(train_csv_path, train_image_dir)\n    \n    if len(train_images) == 0:\n        print(\"Nema učitanih slika! Proverite putanje.\")\n        return\n    \n    print(f\"Učitano {len(train_images)} trening slika\")\n    print(f\"Slika sa clover-om: {sum(has_clover)}\")\n    print(f\"Slika bez clover-a: {len(has_clover) - sum(has_clover)}\")\n    \n    # Skaliranje targeta\n    print(\"Skaliranje target vrednosti...\")\n    scaled_targets = data_loader.scale_targets(train_targets, fit=True)\n    \n    # Građenje i treniranje modela\n    print(\"Građenje ensemble modela...\")\n    ensemble.build_models()\n    \n    print(\"Početak treniranja...\")\n    ensemble.train_ensemble(train_images, scaled_targets, has_clover)\n    \n    # PREDIKCIJA NA TEST SETU\n    print(\"\\nUčitavanje test podataka...\")\n    test_csv_path = os.path.join(data_path, 'test.csv')\n    test_image_dir = data_path\n    \n    test_images, test_image_paths, test_target_names = data_loader.load_test_data(test_csv_path, test_image_dir)\n    \n    if len(test_images) == 0:\n        print(\"Nema test slika! Proverite putanje.\")\n        return\n    \n    print(f\"Učitano {len(test_images)} test slika\")\n    print(\"Pravi se predikcije na test setu...\")\n    test_predictions_scaled, clover_probs = ensemble.predict(test_images)\n    test_predictions = data_loader.inverse_scale_targets(test_predictions_scaled)\n    \n    # Prikaz clover statistike\n    clover_detected = sum(1 for prob in clover_probs if prob > ensemble.clover_threshold)\n    print(f\"Clover detektovan na {clover_detected} od {len(test_images)} test slika\")\n    \n    # KREIRANJE SUBMISSION FAJLA\n    print(\"Kreiranje submission fajla...\")\n    \n    # Učitaj test.csv\n    test_df = pd.read_csv(test_csv_path)\n    \n    # Kreiraj submission DataFrame\n    submission_data = []\n    \n    for i, image_path in enumerate(test_image_paths):\n        for target_name in test_target_names[i]:\n            original_target_name = REVERSE_TARGET_MAPPING[target_name]\n            \n            mask = (test_df['image_path'] == image_path) & (test_df['target_name'] == original_target_name)\n            matching_rows = test_df[mask]\n            \n            for _, row in matching_rows.iterrows():\n                sample_id = row['sample_id']\n                predicted_value = test_predictions[target_name][i]\n                \n                submission_data.append({\n                    'sample_id': sample_id,\n                    'target': predicted_value\n                })\n    \n    submission_df = pd.DataFrame(submission_data)\n    \n    # Provera poklapanja\n    test_sample_ids = set(test_df['sample_id'].unique())\n    submission_sample_ids = set(submission_df['sample_id'].unique())\n    \n    missing_ids = test_sample_ids - submission_sample_ids\n    if len(missing_ids) > 0:\n        print(f\"Upozorenje: {len(missing_ids)} sample_id nedostaje. Popunjavam srednjom vrednošću.\")\n        mean_target = submission_df['target'].mean()\n        \n        for missing_id in missing_ids:\n            submission_df = pd.concat([\n                submission_df, \n                pd.DataFrame([{'sample_id': missing_id, 'target': mean_target}])\n            ], ignore_index=True)\n    \n    # Sortiraj i sačuvaj\n    submission_df = submission_df.sort_values('sample_id').reset_index(drop=True)\n    \n    expected_rows = len(test_df)\n    actual_rows = len(submission_df)\n    \n    if actual_rows != expected_rows:\n        print(f\"Upozorenje: Broj redova u submission fajlu ({actual_rows}) ne odgovara test.csv ({expected_rows})\")\n    \n    # Čuvanje submission fajla U TRENUTNOM DIREKTORIJUMU\n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission fajl sačuvan kao '{submission_path}'\")\n    \n    print(f\"\\nRezime:\")\n    print(f\"Trening slika: {len(train_images)}\")\n    print(f\"Test slika: {len(test_images)}\")\n    print(f\"Clover detektovan na: {clover_detected} test slika\")\n    print(f\"Ukupno predikcija u submission fajlu: {len(submission_df)}\")\n    print(\"Predikcije uspešno generisane!\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T18:35:56.677320Z","iopub.execute_input":"2025-11-03T18:35:56.678062Z","iopub.status.idle":"2025-11-03T19:42:38.063697Z","shell.execute_reply.started":"2025-11-03T18:35:56.678033Z","shell.execute_reply":"2025-11-03T19:42:38.063021Z"}},"outputs":[],"execution_count":null}]}