Upload dl-genai (1).ipynb
Browse files- dl-genai (1).ipynb +1 -0
dl-genai (1).ipynb
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":119874,"databundleVersionId":14372465},{"sourceType":"modelInstanceVersion","sourceId":641877,"databundleVersionId":14449807,"modelInstanceId":483952},{"sourceType":"modelInstanceVersion","sourceId":641700,"databundleVersionId":14448141,"modelInstanceId":483940},{"sourceType":"modelInstanceVersion","sourceId":641715,"databundleVersionId":14448271,"modelInstanceId":483952},{"sourceType":"modelInstanceVersion","sourceId":641870,"databundleVersionId":14449727,"modelInstanceId":483940}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n for filename in filenames:\n print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom kaggle_secrets import UserSecretsClient\nuser_secrets = UserSecretsClient()\nos.environ[\"HF_TOKEN\"] = user_secrets.get_secret(\"entire_hf_write_access\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-09T07:09:34.789435Z","iopub.execute_input":"2025-11-09T07:09:34.789735Z","iopub.status.idle":"2025-11-09T07:09:34.936024Z","shell.execute_reply.started":"2025-11-09T07:09:34.789712Z","shell.execute_reply":"2025-11-09T07:09:34.935031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q trackio kagglehub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-12T08:22:02.202117Z","iopub.execute_input":"2025-11-12T08:22:02.202757Z","iopub.status.idle":"2025-11-12T08:22:08.986432Z","shell.execute_reply.started":"2025-11-12T08:22:02.202729Z","shell.execute_reply":"2025-11-12T08:22:08.985675Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# SECTION A: ENVIRONMENT & DEPENDENCIES\n# ============================================================================\n\n!pip install -q trackio kagglehub\n\nimport os\nimport gc\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport pytorch_lightning as pl\nfrom pytorch_lightning.callbacks import ModelCheckpoint\nfrom sklearn.model_selection import train_test_split\nfrom kaggle_secrets import UserSecretsClient\nimport trackio\nimport kagglehub\n\n# ============================================================================\n# SECTION B: GLOBAL SETTINGS\n# ============================================================================\n\nclass PipelineSettings:\n \"\"\"Manages all static parameters and paths for the project.\"\"\"\n \n def __init__(self):\n # --- Data Source Paths ---\n self.DATA_ROOT_DIR = \"/kaggle/input/sep-25-dl-gen-ai-nppe-1/face_dataset\"\n self.TRAIN_CSV_PATH = f\"{self.DATA_ROOT_DIR}/train.csv\"\n self.TEST_CSV_PATH = f\"{self.DATA_ROOT_DIR}/test.csv\"\n \n # --- Model Hyperparameters ---\n self.INPUT_IMAGE_SIZE = 128\n self.BATCH_SIZE = 128\n self.LEARNING_RATE = 1e-3\n self.NUM_EPOCHS = 10\n self.AGE_LOSS_WEIGHT = 0.01\n \n # --- Model Architecture Selection ---\n self.SCRATCH_MODEL_ID = \"scratch_cnn\"\n self.FINETUNED_MODEL_ID = \"resnet_finetuned\"\n \n # --- System & Environment ---\n self.NUM_DATALOADER_WORKERS = os.cpu_count()\n \n # --- Kaggle Model Upload Settings ---\n self.KAGGLE_USERNAME = None # Will be fetched automatically\n self.MODEL_NAME = \"face-age-gender-predictor\" # Your model name on Kaggle\n\n# Instantiate global settings\nsettings = PipelineSettings()\n\n# ============================================================================\n# SECTION C: IMAGE AUGMENTATION & PREPROCESSING\n# ============================================================================\n\nclass ImageAugmentor:\n \"\"\"Defines image transformation pipelines for training and evaluation.\"\"\"\n \n def __init__(self, image_size):\n self.image_size = image_size\n self.normalization_params = {'mean': [0.485, 0.456, 0.406], 'std': [0.229, 0.224, 0.225]}\n\n def get_training_transforms(self):\n \"\"\"Returns an augmented transformation pipeline for the training set.\"\"\"\n return transforms.Compose([\n transforms.Resize((self.image_size, self.image_size)),\n transforms.RandomHorizontalFlip(p=0.5),\n transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1),\n transforms.ToTensor(),\n transforms.Normalize(**self.normalization_params),\n ])\n \n def get_inference_transforms(self):\n \"\"\"Returns a standard transformation pipeline for validation and testing.\"\"\"\n return transforms.Compose([\n transforms.Resize((self.image_size, self.image_size)),\n transforms.ToTensor(),\n transforms.Normalize(**self.normalization_params),\n ])\n\n# ============================================================================\n# SECTION D: CUSTOM DATASET LOADER\n# ============================================================================\n\nclass FaceImageDataset(Dataset):\n \"\"\"Custom PyTorch Dataset to load facial images and their attributes.\"\"\"\n \n def __init__(self, metadata_df, image_dir, image_transform=None, is_prediction=False):\n self.metadata = metadata_df\n self.image_dir = image_dir\n self.transform = image_transform\n self.is_prediction = is_prediction\n \n def __len__(self):\n return len(self.metadata)\n \n def __getitem__(self, idx):\n row = self.metadata.iloc[idx]\n image_path = os.path.join(self.image_dir, row['full_path'])\n image = Image.open(image_path).convert(\"RGB\")\n \n if self.transform:\n image = self.transform(image)\n \n if self.is_prediction:\n return image\n else:\n gender_target = torch.tensor(row['gender'], dtype=torch.float32)\n age_target = torch.tensor(row['age'], dtype=torch.float32)\n return image, gender_target, age_target\n\n# ============================================================================\n# SECTION E: PYTORCH LIGHTNING DATA MODULE\n# ============================================================================\n\nclass FaceDataModule(pl.LightningDataModule):\n \"\"\"Encapsulates all data loading and splitting logic.\"\"\"\n \n def __init__(self, config: PipelineSettings):\n super().__init__()\n self.cfg = config\n self.augmentor = ImageAugmentor(self.cfg.INPUT_IMAGE_SIZE)\n self.train_df, self.val_df = None, None\n\n def prepare_data(self):\n pass\n\n def setup(self, stage=None):\n if stage == 'fit' or stage is None:\n full_train_data = pd.read_csv(self.cfg.TRAIN_CSV_PATH)\n self.train_df, self.val_df = train_test_split(\n full_train_data, \n test_size=0.15, \n random_state=42, \n stratify=full_train_data['gender']\n )\n \n self.train_dataset = FaceImageDataset(\n self.train_df, \n self.cfg.DATA_ROOT_DIR,\n self.augmentor.get_training_transforms()\n )\n \n self.val_dataset = FaceImageDataset(\n self.val_df,\n self.cfg.DATA_ROOT_DIR,\n self.augmentor.get_inference_transforms()\n )\n \n def train_dataloader(self):\n return DataLoader(self.train_dataset, batch_size=self.cfg.BATCH_SIZE, shuffle=True, num_workers=self.cfg.NUM_DATALOADER_WORKERS)\n \n def val_dataloader(self):\n return DataLoader(self.val_dataset, batch_size=self.cfg.BATCH_SIZE, num_workers=self.cfg.NUM_DATALOADER_WORKERS)\n\n# ============================================================================\n# SECTION F: ABSTRACT MODEL DEFINITION\n# ============================================================================\n\nclass AbstractFaceModel(pl.LightningModule):\n \"\"\"A base class for face attribute models, defining the training loop and loss.\"\"\"\n \n def __init__(self, learning_rate, age_loss_weight):\n super().__init__()\n self.save_hyperparameters()\n self.lr = learning_rate\n self.age_weight = age_loss_weight\n self.gender_loss_fn = nn.BCEWithLogitsLoss()\n self.age_loss_fn = nn.MSELoss()\n\n def _calculate_combined_loss(self, gender_preds, age_preds, gender_labels, age_labels):\n gender_loss = self.gender_loss_fn(gender_preds.squeeze(), gender_labels)\n age_loss = self.age_loss_fn(age_preds.squeeze(), age_labels)\n total_loss = gender_loss + (age_loss * self.age_weight)\n return total_loss\n\n def training_step(self, batch, batch_idx):\n images, gender_labels, age_labels = batch\n gender_preds, age_preds = self(images)\n loss = self._calculate_combined_loss(gender_preds, age_preds, gender_labels, age_labels)\n self.log('train_loss', loss, on_step=True, on_epoch=True, prog_bar=True)\n return loss\n \n def validation_step(self, batch, batch_idx):\n images, gender_labels, age_labels = batch\n gender_preds, age_preds = self(images)\n loss = self._calculate_combined_loss(gender_preds, age_preds, gender_labels, age_labels)\n self.log('val_loss', loss, on_epoch=True, prog_bar=True)\n \n def configure_optimizers(self):\n optimizer = torch.optim.Adam(self.parameters(), lr=self.lr)\n return optimizer\n\n# ============================================================================\n# SECTION G: CUSTOM SCRATCH CNN MODEL\n# ============================================================================\n\nclass ScratchCNNModel(AbstractFaceModel):\n \"\"\"A custom Convolutional Neural Network built from scratch.\"\"\"\n \n def __init__(self, learning_rate, age_loss_weight):\n super().__init__(learning_rate, age_loss_weight)\n \n def conv_block(in_f, out_f):\n return nn.Sequential(\n nn.Conv2d(in_f, out_f, kernel_size=3, padding=1, bias=False),\n nn.BatchNorm2d(out_f),\n nn.ReLU(inplace=True),\n nn.MaxPool2d(kernel_size=2, stride=2)\n )\n\n self.feature_extractor = nn.Sequential(\n conv_block(3, 32),\n conv_block(32, 64),\n conv_block(64, 128),\n conv_block(128, 256),\n )\n \n probe_tensor = torch.randn(1, 3, settings.INPUT_IMAGE_SIZE, settings.INPUT_IMAGE_SIZE)\n flattened_size = self.feature_extractor(probe_tensor).view(1, -1).size(1)\n \n self.gender_head = nn.Linear(flattened_size, 1)\n self.age_head = nn.Linear(flattened_size, 1)\n\n def forward(self, x):\n features = self.feature_extractor(x)\n features = torch.flatten(features, 1)\n gender_output = self.gender_head(features)\n age_output = self.age_head(features)\n return gender_output, age_output\n\n# ============================================================================\n# SECTION H: TRANSFER LEARNING MODEL (ResNet18)\n# ============================================================================\n\nclass FineTunedResNetModel(AbstractFaceModel):\n \"\"\"A model using a pre-trained ResNet18 backbone for feature extraction.\"\"\"\n \n def __init__(self, learning_rate, age_loss_weight):\n super().__init__(learning_rate, age_loss_weight)\n resnet = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n num_features = resnet.fc.in_features\n \n self.backbone = nn.Sequential(*list(resnet.children())[:-1])\n \n self.gender_head = nn.Linear(num_features, 1)\n self.age_head = nn.Linear(num_features, 1)\n\n def forward(self, x):\n features = self.backbone(x)\n features = torch.flatten(features, 1)\n gender_output = self.gender_head(features)\n age_output = self.age_head(features)\n return gender_output, age_output\n\n# ============================================================================\n# SECTION I: EXECUTION & ORCHESTRATION\n# ============================================================================\n\nclass PipelineRunner:\n \"\"\"Coordinates training and model upload workflow.\"\"\"\n \n def __init__(self, cfg: PipelineSettings):\n self.cfg = cfg\n self.data_module = FaceDataModule(cfg)\n self._authenticate_trackio()\n self._get_kaggle_username()\n\n def _authenticate_trackio(self):\n try:\n secrets = UserSecretsClient()\n hf_token = secrets.get_secret(\"HUGGINGFACE_TOKEN\") # Store in Kaggle Secrets\n os.environ[\"HF_TOKEN\"] = hf_token\n print(\"โ TrackIO authentication configured.\")\n except Exception as e:\n print(f\"โ Could not configure TrackIO authentication: {e}\")\n\n def _get_kaggle_username(self):\n \"\"\"Fetch Kaggle username for model upload.\"\"\"\n try:\n user_info = kagglehub.whoami()\n self.cfg.KAGGLE_USERNAME = user_info['username']\n print(f\"โ Kaggle username: {self.cfg.KAGGLE_USERNAME}\")\n except Exception as e:\n print(f\"โ Could not fetch Kaggle username: {e}\")\n self.cfg.KAGGLE_USERNAME = \"your-username\" # Fallback\n\n def _run_training_session(self, model_instance, model_name, tracking_id):\n print(f\"\\n{'='*70}\\n๐ Starting Training: {model_name}\\n{'='*70}\")\n \n trackio.init(\n project=\"25-t3-nppe1\",\n name=tracking_id,\n config={\n \"space_id\": \"josondev/IITM-NPPE\",\n \"learning_rate\": self.cfg.LEARNING_RATE,\n \"epochs\": self.cfg.NUM_EPOCHS,\n \"model_type\": model_name\n }\n )\n \n checkpoint_cb = ModelCheckpoint(\n monitor='val_loss',\n dirpath='/kaggle/working/',\n filename=f'{model_name}-best-model',\n save_top_k=1,\n mode='min'\n )\n \n trainer = pl.Trainer(\n max_epochs=self.cfg.NUM_EPOCHS,\n accelerator='gpu',\n devices='auto',\n strategy=\"ddp_notebook\",\n callbacks=[checkpoint_cb]\n )\n \n trainer.fit(model_instance, self.data_module)\n print(f\"โ
Best checkpoint saved at: {checkpoint_cb.best_model_path}\")\n \n final_val_loss = trainer.callback_metrics.get('val_loss', torch.tensor(0.0)).item()\n trackio.log({\"final_validation_loss\": final_val_loss})\n trackio.finish()\n \n # Upload to Kaggle Hub\n self._upload_to_kaggle_hub(checkpoint_cb.best_model_path, model_name, final_val_loss)\n \n del model_instance, trainer, checkpoint_cb\n gc.collect()\n torch.cuda.empty_cache()\n\n def _upload_to_kaggle_hub(self, checkpoint_path, model_name, val_loss):\n \"\"\"Upload trained checkpoint to Kaggle Models.\"\"\"\n print(f\"\\n{'='*70}\\n๐ค Uploading {model_name} to Kaggle Hub...\\n{'='*70}\")\n \n try:\n # Define handle: username/model/framework/variation\n framework = \"pyTorch\"\n variation = model_name.replace(\"_\", \"-\") # e.g., \"scratch-cnn\" or \"resnet-finetuned\"\n handle = f\"{self.cfg.KAGGLE_USERNAME}/{self.cfg.MODEL_NAME}/{framework}/{variation}\"\n \n # Create a directory with checkpoint and metadata\n upload_dir = f\"/kaggle/working/{model_name}_upload\"\n os.makedirs(upload_dir, exist_ok=True)\n \n # Copy checkpoint\n import shutil\n shutil.copy(checkpoint_path, os.path.join(upload_dir, \"model.ckpt\"))\n \n # Create a README (optional but recommended)\n with open(os.path.join(upload_dir, \"README.md\"), \"w\") as f:\n f.write(f\"# {model_name.replace('_', ' ').title()} Model\\n\\n\")\n f.write(f\"**Validation Loss:** {val_loss:.4f}\\n\\n\")\n f.write(f\"**Framework:** PyTorch Lightning\\n\\n\")\n f.write(f\"**Task:** Gender classification (binary) + Age regression\\n\\n\")\n f.write(f\"Trained on face dataset with {self.cfg.NUM_EPOCHS} epochs.\\n\")\n \n # Upload using kagglehub\n version_notes = f\"Val loss: {val_loss:.4f}, Epochs: {self.cfg.NUM_EPOCHS}\"\n kagglehub.model_upload(\n handle=handle,\n local_model_dir=upload_dir,\n version_notes=version_notes,\n license_name=\"Apache 2.0\"\n )\n \n print(f\"โ
Model uploaded successfully!\")\n print(f\" View at: https://www.kaggle.com/models/{self.cfg.KAGGLE_USERNAME}/{self.cfg.MODEL_NAME}/{framework}/{variation}\")\n \n except Exception as e:\n print(f\"โ Upload failed: {e}\")\n print(\" You can manually upload from /kaggle/working/ via Kaggle UI or dataset creation.\")\n\n def execute(self):\n \"\"\"Main entry point: Training only (no inference).\"\"\"\n print(\"\\n*** MODE: TRAINING + UPLOAD ***\")\n \n # Train Scratch Model\n scratch_model = ScratchCNNModel(self.cfg.LEARNING_RATE, self.cfg.AGE_LOSS_WEIGHT)\n self._run_training_session(scratch_model, \"scratch_cnn\", \"BaselineCNN-v2\")\n\n # Train Finetuned Model\n finetuned_model = FineTunedResNetModel(self.cfg.LEARNING_RATE, self.cfg.AGE_LOSS_WEIGHT)\n self._run_training_session(finetuned_model, \"resnet_finetuned\", \"TransferResNet-v2\")\n \n print(\"\\n๐ Training and upload pipeline completed!\")\n print(\" Both models are now available on Kaggle Models for inference.\")\n\n# ============================================================================\n# SECTION J: SCRIPT EXECUTION\n# ============================================================================\n\nif __name__ == \"__main__\":\n pipeline = PipelineRunner(settings)\n pipeline.execute()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-12T08:22:08.988265Z","iopub.execute_input":"2025-11-12T08:22:08.988499Z","iopub.status.idle":"2025-11-12T08:22:09.691837Z","shell.execute_reply.started":"2025-11-12T08:22:08.988475Z","shell.execute_reply":"2025-11-12T08:22:09.690620Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls -lh /kaggle/working | grep -i submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-12T08:22:09.692226Z","iopub.status.idle":"2025-11-12T08:22:09.692460Z","shell.execute_reply.started":"2025-11-12T08:22:09.692346Z","shell.execute_reply":"2025-11-12T08:22:09.692357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# INFERENCE NOTEBOOK: Download Models from Kaggle Hub & Generate Submissions\n# ============================================================================\n# This notebook loads pre-trained models from Kaggle Models and creates\n# submission_scratch.csv and submission_finetuned.csv for competition upload.\n\n!pip install -q kagglehub\n\nimport os\nimport gc\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport pytorch_lightning as pl\nimport kagglehub\n\n# ============================================================================\n# SECTION A: SETTINGS & PATHS\n# ============================================================================\n\nclass InferenceSettings:\n \"\"\"Configuration for inference pipeline.\"\"\"\n \n def __init__(self):\n # Data paths\n self.DATA_ROOT_DIR = \"/kaggle/input/sep-25-dl-gen-ai-nppe-1/face_dataset\"\n self.TEST_CSV_PATH = f\"{self.DATA_ROOT_DIR}/test.csv\"\n \n # Model parameters (must match training)\n self.INPUT_IMAGE_SIZE = 128\n self.BATCH_SIZE = 128\n self.LEARNING_RATE = 1e-3\n self.AGE_LOSS_WEIGHT = 0.01\n \n # Kaggle Model Hub handles (UPDATE with your username)\n self.KAGGLE_USERNAME = \"rexjosondeva\" # Replace with your Kaggle username\n self.MODEL_NAME = \"face-age-gender-predictor\"\n self.SCRATCH_HANDLE = f\"{self.KAGGLE_USERNAME}/{self.MODEL_NAME}/pyTorch/scratch-cnn\"\n self.FINETUNED_HANDLE = f\"{self.KAGGLE_USERNAME}/{self.MODEL_NAME}/pyTorch/resnet-finetuned\"\n \n # System\n self.NUM_WORKERS = 2 # Reduce for stability\n\nsettings = InferenceSettings()\n\n# ============================================================================\n# SECTION B: DATA LOADING\n# ============================================================================\n\nclass ImageAugmentor:\n \"\"\"Inference-time image transforms (no augmentation).\"\"\"\n def __init__(self, image_size=128):\n self.transform = transforms.Compose([\n transforms.Resize((image_size, image_size)),\n transforms.ToTensor(),\n transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n ])\n\nclass FaceImageTestDataset(Dataset):\n \"\"\"Test dataset loader.\"\"\"\n def __init__(self, metadata_df, image_dir, transform=None):\n self.metadata = metadata_df\n self.image_dir = image_dir\n self.transform = transform\n \n def __len__(self):\n return len(self.metadata)\n \n def __getitem__(self, idx):\n row = self.metadata.iloc[idx]\n image_path = os.path.join(self.image_dir, row['full_path'])\n image = Image.open(image_path).convert(\"RGB\")\n if self.transform:\n image = self.transform(image)\n return image\n\n# Load test data\ntest_df = pd.read_csv(settings.TEST_CSV_PATH)\naugmentor = ImageAugmentor(settings.INPUT_IMAGE_SIZE)\ntest_dataset = FaceImageTestDataset(test_df, settings.DATA_ROOT_DIR, augmentor.transform)\ntest_loader = DataLoader(test_dataset, batch_size=settings.BATCH_SIZE, shuffle=False, num_workers=settings.NUM_WORKERS)\n\nprint(f\"โ
Test dataset loaded: {len(test_df)} samples\")\n\n# ============================================================================\n# SECTION C: MODEL DEFINITIONS (Must Match Training)\n# ============================================================================\n\nclass ScratchCNNModel(pl.LightningModule):\n \"\"\"Custom CNN from scratch.\"\"\"\n def __init__(self, learning_rate, age_loss_weight):\n super().__init__()\n self.save_hyperparameters()\n \n def conv_block(in_f, out_f):\n return nn.Sequential(\n nn.Conv2d(in_f, out_f, kernel_size=3, padding=1, bias=False),\n nn.BatchNorm2d(out_f),\n nn.ReLU(inplace=True),\n nn.MaxPool2d(kernel_size=2, stride=2)\n )\n \n self.feature_extractor = nn.Sequential(\n conv_block(3, 32),\n conv_block(32, 64),\n conv_block(64, 128),\n conv_block(128, 256),\n )\n \n probe = torch.randn(1, 3, 128, 128)\n flat_size = self.feature_extractor(probe).view(1, -1).size(1)\n self.gender_head = nn.Linear(flat_size, 1)\n self.age_head = nn.Linear(flat_size, 1)\n \n def forward(self, x):\n features = self.feature_extractor(x)\n features = torch.flatten(features, 1)\n return self.gender_head(features), self.age_head(features)\n\nclass FineTunedResNetModel(pl.LightningModule):\n \"\"\"Fine-tuned ResNet18 model.\"\"\"\n def __init__(self, learning_rate, age_loss_weight):\n super().__init__()\n self.save_hyperparameters()\n resnet = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n num_features = resnet.fc.in_features\n self.backbone = nn.Sequential(*list(resnet.children())[:-1])\n self.gender_head = nn.Linear(num_features, 1)\n self.age_head = nn.Linear(num_features, 1)\n \n def forward(self, x):\n features = self.backbone(x)\n features = torch.flatten(features, 1)\n return self.gender_head(features), self.age_head(features)\n\n# ============================================================================\n# SECTION D: DOWNLOAD MODELS FROM KAGGLE HUB\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"๐ฅ Downloading Models from Kaggle Hub...\")\nprint(\"=\"*70)\n\n# Download scratch model\nprint(f\"\\nโ Downloading Scratch CNN from: {settings.SCRATCH_HANDLE}\")\nscratch_model_dir = kagglehub.model_download(settings.SCRATCH_HANDLE)\nscratch_ckpt_path = os.path.join(scratch_model_dir, \"model.ckpt\")\nprint(f\" โ
Downloaded to: {scratch_ckpt_path}\")\n\n# Download finetuned model\nprint(f\"\\nโ Downloading Fine-Tuned ResNet from: {settings.FINETUNED_HANDLE}\")\nfinetuned_model_dir = kagglehub.model_download(settings.FINETUNED_HANDLE)\nfinetuned_ckpt_path = os.path.join(finetuned_model_dir, \"model.ckpt\")\nprint(f\" โ
Downloaded to: {finetuned_ckpt_path}\")\n\n# Verify files exist\nassert os.path.exists(scratch_ckpt_path), f\"Scratch checkpoint not found: {scratch_ckpt_path}\"\nassert os.path.exists(finetuned_ckpt_path), f\"Finetuned checkpoint not found: {finetuned_ckpt_path}\"\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"\\nโ
Using device: {device}\")\n\n# ============================================================================\n# SECTION E: INFERENCE - SCRATCH MODEL\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"๐ฎ Running Scratch CNN Predictions...\")\nprint(\"=\"*70)\n\nscratch_model = ScratchCNNModel.load_from_checkpoint(\n checkpoint_path=scratch_ckpt_path,\n learning_rate=settings.LEARNING_RATE,\n age_loss_weight=settings.AGE_LOSS_WEIGHT\n)\nscratch_model.eval()\nscratch_model.to(device)\n\nscratch_gender_logits, scratch_age_outputs = [], []\nwith torch.no_grad():\n for images in test_loader:\n images = images.to(device)\n gender_pred, age_pred = scratch_model(images)\n scratch_gender_logits.append(gender_pred.cpu())\n scratch_age_outputs.append(age_pred.cpu())\n\n# Post-process predictions\nscratch_gender_logits = torch.cat(scratch_gender_logits).numpy().reshape(-1)\nscratch_age_outputs = torch.cat(scratch_age_outputs).numpy().reshape(-1)\nscratch_gender_probs = 1 / (1 + np.exp(-scratch_gender_logits))\nscratch_gender_preds = (scratch_gender_probs > 0.5).astype(int)\nscratch_age_preds = np.clip(scratch_age_outputs, 0, 120)\n\n# Save submission\nsubmission_scratch = pd.DataFrame({\n 'id': test_df['id'].values,\n 'gender': scratch_gender_preds,\n 'age': scratch_age_preds\n})\nsubmission_scratch.to_csv('/kaggle/working/submission_scratch.csv', index=False)\n\nprint(\"โ
Scratch CNN submission saved: submission_scratch.csv\")\nprint(submission_scratch.head())\nprint(f\"Shape: {submission_scratch.shape}\")\n\n# Free memory\ndel scratch_model, scratch_gender_logits, scratch_age_outputs\ngc.collect()\ntorch.cuda.empty_cache()\n\n# ============================================================================\n# SECTION F: INFERENCE - FINETUNED MODEL\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"๐ฎ Running Fine-Tuned ResNet Predictions...\")\nprint(\"=\"*70)\n\nfinetuned_model = FineTunedResNetModel.load_from_checkpoint(\n checkpoint_path=finetuned_ckpt_path,\n learning_rate=settings.LEARNING_RATE,\n age_loss_weight=settings.AGE_LOSS_WEIGHT\n)\nfinetuned_model.eval()\nfinetuned_model.to(device)\n\nfinetuned_gender_logits, finetuned_age_outputs = [], []\nwith torch.no_grad():\n for images in test_loader:\n images = images.to(device)\n gender_pred, age_pred = finetuned_model(images)\n finetuned_gender_logits.append(gender_pred.cpu())\n finetuned_age_outputs.append(age_pred.cpu())\n\n# Post-process predictions\nfinetuned_gender_logits = torch.cat(finetuned_gender_logits).numpy().reshape(-1)\nfinetuned_age_outputs = torch.cat(finetuned_age_outputs).numpy().reshape(-1)\nfinetuned_gender_probs = 1 / (1 + np.exp(-finetuned_gender_logits))\nfinetuned_gender_preds = (finetuned_gender_probs > 0.5).astype(int)\nfinetuned_age_preds = np.clip(finetuned_age_outputs, 0, 120)\n\n# Save submission\nsubmission_finetuned = pd.DataFrame({\n 'id': test_df['id'].values,\n 'gender': finetuned_gender_preds,\n 'age': finetuned_age_preds\n})\nsubmission_finetuned.to_csv('/kaggle/working/submission_finetuned.csv', index=False)\n\nprint(\"โ
Fine-Tuned ResNet submission saved: submission_finetuned.csv\")\nprint(submission_finetuned.head())\nprint(f\"Shape: {submission_finetuned.shape}\")\n\n# Free memory\ndel finetuned_model, finetuned_gender_logits, finetuned_age_outputs\ngc.collect()\ntorch.cuda.empty_cache()\n\n# ============================================================================\n# SECTION G: OPTIONAL ENSEMBLE\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"๐ฏ Generating Ensemble Submission (Average of Both Models)...\")\nprint(\"=\"*70)\n\nensemble_gender = ((scratch_gender_preds + finetuned_gender_preds) / 2 > 0.5).astype(int)\nensemble_age = (scratch_age_preds + finetuned_age_preds) / 2\n\nsubmission_ensemble = pd.DataFrame({\n 'id': test_df['id'].values,\n 'gender': ensemble_gender,\n 'age': ensemble_age\n})\nsubmission_ensemble.to_csv('/kaggle/working/submission_ensemble.csv', index=False)\n\nprint(\"โ
Ensemble submission saved: submission_ensemble.csv\")\nprint(submission_ensemble.head())\nprint(f\"Shape: {submission_ensemble.shape}\")\n\n# ============================================================================\n# FINAL SUMMARY\n# ============================================================================\n\nprint(\"\\n\" + \"=\"*70)\nprint(\"๐ ALL SUBMISSIONS GENERATED SUCCESSFULLY!\")\nprint(\"=\"*70)\nprint(\"Files in /kaggle/working/:\")\nprint(\" - submission_scratch.csv\")\nprint(\" - submission_finetuned.csv\")\nprint(\" - submission_ensemble.csv\")\nprint(\"\\nDownload from Output panel and submit to competition!\")\nprint(\"=\"*70)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-12T10:45:51.133717Z","iopub.execute_input":"2025-11-12T10:45:51.134423Z","iopub.status.idle":"2025-11-12T10:47:19.922939Z","shell.execute_reply.started":"2025-11-12T10:45:51.134393Z","shell.execute_reply":"2025-11-12T10:47:19.922179Z"}},"outputs":[{"name":"stdout","text":"โ
Test dataset loaded: 8677 samples\n\n======================================================================\n๐ฅ Downloading Models from Kaggle Hub...\n======================================================================\n\nโ Downloading Scratch CNN from: rexjosondeva/face-age-gender-predictor/pyTorch/scratch-cnn\nMounting files to /kaggle/input/face-age-gender-predictor/pytorch/scratch-cnn/2...\n โ
Downloaded to: /kaggle/input/face-age-gender-predictor/pytorch/scratch-cnn/2/model.ckpt\n\nโ Downloading Fine-Tuned ResNet from: rexjosondeva/face-age-gender-predictor/pyTorch/resnet-finetuned\n โ
Downloaded to: /kaggle/input/face-age-gender-predictor/pytorch/resnet-finetuned/2/model.ckpt\n\nโ
Using device: cuda\n\n======================================================================\n๐ฎ Running Scratch CNN Predictions...\n======================================================================\nโ
Scratch CNN submission saved: submission_scratch.csv\n id gender age\n0 0 1 30.513975\n1 1 0 22.544374\n2 2 1 29.283455\n3 3 1 29.424118\n4 4 1 51.745323\nShape: (8677, 3)\n\n======================================================================\n๐ฎ Running Fine-Tuned ResNet Predictions...\n======================================================================\nโ
Fine-Tuned ResNet submission saved: submission_finetuned.csv\n id gender age\n0 0 1 35.028824\n1 1 0 27.688492\n2 2 1 35.269264\n3 3 1 28.340361\n4 4 1 64.935043\nShape: (8677, 3)\n\n======================================================================\n๐ฏ Generating Ensemble Submission (Average of Both Models)...\n======================================================================\nโ
Ensemble submission saved: submission_ensemble.csv\n id gender age\n0 0 1 32.771400\n1 1 0 25.116432\n2 2 1 32.276360\n3 3 1 28.882240\n4 4 1 58.340183\nShape: (8677, 3)\n\n======================================================================\n๐ ALL SUBMISSIONS GENERATED SUCCESSFULLY!\n======================================================================\nFiles in /kaggle/working/:\n - submission_scratch.csv\n - submission_finetuned.csv\n - submission_ensemble.csv\n\nDownload from Output panel and submit to competition!\n======================================================================\n","output_type":"stream"}],"execution_count":15},{"cell_type":"code","source":"# ============================================================================\n# SCRATCH CNN INFERENCE (Fixed Layer Names)\n# ============================================================================\n\nimport os\nimport gc\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nimport pytorch_lightning as pl\n\n# Settings\nclass Config:\n DATA_ROOT = \"/kaggle/input/sep-25-dl-gen-ai-nppe-1/face_dataset\"\n TEST_CSV = f\"{DATA_ROOT}/test.csv\"\n IMG_SIZE = 128\n BATCH = 128\n LR = 1e-3\n AGE_WEIGHT = 0.01\n WORKERS = 2\n\ncfg = Config()\n\n# Auto-detect checkpoint\nprint(\"=\"*70)\nprint(\"๐ Searching for scratch model checkpoint...\")\nprint(\"=\"*70)\n\ncfg.CHECKPOINT = None\nfor root, dirs, files in os.walk(\"/kaggle/input/\"):\n for file in files:\n if file == \"model.ckpt\" and \"scratch\" in root.lower():\n cfg.CHECKPOINT = os.path.join(root, file)\n print(f\"โ
Found: {cfg.CHECKPOINT}\")\n break\n if cfg.CHECKPOINT:\n break\n\nif cfg.CHECKPOINT is None:\n raise FileNotFoundError(\"Add Kaggle Model: face-age-gender-predictor (scratch-cnn) via 'Add Input'\")\n\n# Data Pipeline\nclass ImageTransform:\n def __init__(self, size=128):\n self.transform = transforms.Compose([\n transforms.Resize((size, size)),\n transforms.ToTensor(),\n transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n ])\n\nclass TestDataset(Dataset):\n def __init__(self, df, root, tfm):\n self.df, self.root, self.tfm = df, root, tfm\n def __len__(self): return len(self.df)\n def __getitem__(self, i):\n img = Image.open(os.path.join(self.root, self.df.iloc[i]['full_path'])).convert(\"RGB\")\n return self.tfm(img)\n\ntest_df = pd.read_csv(cfg.TEST_CSV)\ntest_ds = TestDataset(test_df, cfg.DATA_ROOT, ImageTransform(cfg.IMG_SIZE).transform)\ntest_dl = DataLoader(test_ds, batch_size=cfg.BATCH, shuffle=False, num_workers=cfg.WORKERS)\nprint(f\"โ
Test: {len(test_df)} samples\")\n\n# Model (FIXED: Match training architecture with \"feature_extractor\")\nclass ScratchCNN(pl.LightningModule):\n def __init__(self, lr, age_w):\n super().__init__()\n self.save_hyperparameters()\n \n def conv_block(in_c, out_c):\n return nn.Sequential(\n nn.Conv2d(in_c, out_c, 3, padding=1, bias=False),\n nn.BatchNorm2d(out_c),\n nn.ReLU(inplace=True),\n nn.MaxPool2d(2, 2)\n )\n \n # MUST be named \"feature_extractor\" to match checkpoint\n self.feature_extractor = nn.Sequential(\n conv_block(3, 32),\n conv_block(32, 64),\n conv_block(64, 128),\n conv_block(128, 256),\n )\n \n probe = torch.randn(1, 3, 128, 128)\n flat_size = self.feature_extractor(probe).view(1, -1).size(1)\n self.gender_head = nn.Linear(flat_size, 1)\n self.age_head = nn.Linear(flat_size, 1)\n \n def forward(self, x):\n features = torch.flatten(self.feature_extractor(x), 1)\n return self.gender_head(features), self.age_head(features)\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"โ
Device: {device}\\n\")\n\n# Inference\nprint(\"=\"*70)\nprint(\"๐ฎ Scratch CNN Predictions...\")\nprint(\"=\"*70)\n\nmodel = ScratchCNN.load_from_checkpoint(cfg.CHECKPOINT, lr=cfg.LR, age_w=cfg.AGE_WEIGHT)\nmodel.eval().to(device)\n\ng_logits, a_outputs = [], []\nwith torch.no_grad():\n for imgs in test_dl:\n g, a = model(imgs.to(device))\n g_logits.append(g.cpu())\n a_outputs.append(a.cpu())\n\ng_logits = torch.cat(g_logits).numpy().flatten()\na_outputs = torch.cat(a_outputs).numpy().flatten()\ng_probs = 1 / (1 + np.exp(-g_logits))\ng_preds = (g_probs > 0.5).astype(int)\na_preds = np.clip(a_outputs, 0, 120)\n\nsubmission = pd.DataFrame({'id': test_df['id'], 'gender': g_preds, 'age': a_preds})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(\"โ
submission.csv (Scratch CNN)\")\nprint(submission.head())\nprint(f\"Shape: {submission.shape}\")\nprint(\"\\n๐ Ready for submission!\")\n\ndel model; gc.collect(); torch.cuda.empty_cache()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-12T10:47:19.924459Z","iopub.execute_input":"2025-11-12T10:47:19.924773Z","iopub.status.idle":"2025-11-12T10:47:34.689258Z","shell.execute_reply.started":"2025-11-12T10:47:19.924729Z","shell.execute_reply":"2025-11-12T10:47:34.688401Z"}},"outputs":[{"name":"stdout","text":"======================================================================\n๐ Searching for scratch model checkpoint...\n======================================================================\nโ
Found: /kaggle/input/face-age-gender-predictor/pytorch/scratch-cnn/1/model.ckpt\nโ
Test: 8677 samples\nโ
Device: cuda\n\n======================================================================\n๐ฎ Scratch CNN Predictions...\n======================================================================\nโ
submission.csv (Scratch CNN)\n id gender age\n0 0 1 33.268768\n1 1 0 24.642073\n2 2 1 34.242413\n3 3 1 27.935734\n4 4 1 50.617668\nShape: (8677, 3)\n\n๐ Ready for submission!\n","output_type":"stream"}],"execution_count":16},{"cell_type":"code","source":"# ============================================================================\n# FINE-TUNED RESNET INFERENCE (Fixed Layer Names)\n# ============================================================================\n\nimport os\nimport gc\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport pytorch_lightning as pl\n\n# Settings\nclass Config:\n DATA_ROOT = \"/kaggle/input/sep-25-dl-gen-ai-nppe-1/face_dataset\"\n TEST_CSV = f\"{DATA_ROOT}/test.csv\"\n IMG_SIZE = 128\n BATCH = 128\n LR = 1e-3\n AGE_WEIGHT = 0.01\n WORKERS = 2\n\ncfg = Config()\n\n# Auto-detect checkpoint\nprint(\"=\"*70)\nprint(\"๐ Searching for finetuned model checkpoint...\")\nprint(\"=\"*70)\n\ncfg.CHECKPOINT = None\nfor root, dirs, files in os.walk(\"/kaggle/input/\"):\n for file in files:\n if file == \"model.ckpt\" and (\"resnet\" in root.lower() or \"finetuned\" in root.lower() or \"finetune\" in root.lower()):\n cfg.CHECKPOINT = os.path.join(root, file)\n print(f\"โ
Found: {cfg.CHECKPOINT}\")\n break\n if cfg.CHECKPOINT:\n break\n\nif cfg.CHECKPOINT is None:\n raise FileNotFoundError(\"Add Kaggle Model: face-age-gender-predictor (resnet-finetuned) via 'Add Input'\")\n\n# Data Pipeline\nclass ImageTransform:\n def __init__(self, size=128):\n self.transform = transforms.Compose([\n transforms.Resize((size, size)),\n transforms.ToTensor(),\n transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n ])\n\nclass TestDataset(Dataset):\n def __init__(self, df, root, tfm):\n self.df, self.root, self.tfm = df, root, tfm\n def __len__(self): return len(self.df)\n def __getitem__(self, i):\n img = Image.open(os.path.join(self.root, self.df.iloc[i]['full_path'])).convert(\"RGB\")\n return self.tfm(img)\n\ntest_df = pd.read_csv(cfg.TEST_CSV)\ntest_ds = TestDataset(test_df, cfg.DATA_ROOT, ImageTransform(cfg.IMG_SIZE).transform)\ntest_dl = DataLoader(test_ds, batch_size=cfg.BATCH, shuffle=False, num_workers=cfg.WORKERS)\nprint(f\"โ
Test: {len(test_df)} samples\")\n\n# Model (Match training architecture exactly)\nclass FineTunedResNet(pl.LightningModule):\n def __init__(self, lr, age_w):\n super().__init__()\n self.save_hyperparameters()\n resnet = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n num_features = resnet.fc.in_features\n self.backbone = nn.Sequential(*list(resnet.children())[:-1])\n self.gender_head = nn.Linear(num_features, 1)\n self.age_head = nn.Linear(num_features, 1)\n \n def forward(self, x):\n features = torch.flatten(self.backbone(x), 1)\n return self.gender_head(features), self.age_head(features)\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"โ
Device: {device}\\n\")\n\n# Inference\nprint(\"=\"*70)\nprint(\"๐ฎ Fine-Tuned ResNet Predictions...\")\nprint(\"=\"*70)\n\nmodel = FineTunedResNet.load_from_checkpoint(cfg.CHECKPOINT, lr=cfg.LR, age_w=cfg.AGE_WEIGHT)\nmodel.eval().to(device)\n\ng_logits, a_outputs = [], []\nwith torch.no_grad():\n for imgs in test_dl:\n g, a = model(imgs.to(device))\n g_logits.append(g.cpu())\n a_outputs.append(a.cpu())\n\ng_logits = torch.cat(g_logits).numpy().flatten()\na_outputs = torch.cat(a_outputs).numpy().flatten()\ng_probs = 1 / (1 + np.exp(-g_logits))\ng_preds = (g_probs > 0.5).astype(int)\na_preds = np.clip(a_outputs, 0, 120)\n\nsubmission = pd.DataFrame({'id': test_df['id'], 'gender': g_preds, 'age': a_preds})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(\"โ
submission.csv (Fine-Tuned ResNet)\")\nprint(submission.head())\nprint(f\"Shape: {submission.shape}\")\nprint(\"\\n๐ Ready for submission!\")\n\ndel model; gc.collect(); torch.cuda.empty_cache()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-12T10:47:42.672984Z","iopub.execute_input":"2025-11-12T10:47:42.673783Z","iopub.status.idle":"2025-11-12T10:47:57.826931Z","shell.execute_reply.started":"2025-11-12T10:47:42.673755Z","shell.execute_reply":"2025-11-12T10:47:57.826236Z"}},"outputs":[{"name":"stdout","text":"======================================================================\n๐ Searching for finetuned model checkpoint...\n======================================================================\nโ
Found: /kaggle/input/face-age-gender-predictor/pytorch/resnet-finetuned/1/model.ckpt\nโ
Test: 8677 samples\nโ
Device: cuda\n\n======================================================================\n๐ฎ Fine-Tuned ResNet Predictions...\n======================================================================\nโ
submission.csv (Fine-Tuned ResNet)\n id gender age\n0 0 1 33.796421\n1 1 0 27.736071\n2 2 1 35.373379\n3 3 1 24.109423\n4 4 1 64.067947\nShape: (8677, 3)\n\n๐ Ready for submission!\n","output_type":"stream"}],"execution_count":17},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}
|