{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":127713,"databundleVersionId":15291341,"sourceType":"competition"}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nimport torch\nimport torch.nn as nn\nfrom torchvision.transforms import transforms\nfrom torch.utils.data import Dataset,DataLoader\n\nfrom PIL import Image\nimport os\n\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import *","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-06T08:42:41.156534Z","iopub.execute_input":"2026-02-06T08:42:41.157328Z","iopub.status.idle":"2026-02-06T08:42:48.715105Z","shell.execute_reply.started":"2026-02-06T08:42:41.157295Z","shell.execute_reply":"2026-02-06T08:42:48.714549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class location:\n    train_csv = r'/kaggle/input/anime-gender-classification/dataset/train.csv'\n    val_csv = r'/kaggle/input/anime-gender-classification/dataset/val.csv'\n    test_csv = r'/kaggle/input/anime-gender-classification/dataset/test.csv'\n\n    train_img = r'/kaggle/input/anime-gender-classification/dataset/train_images'\n    val_img = r'/kaggle/input/anime-gender-classification/dataset/val_images'\n    test_img = r'/kaggle/input/anime-gender-classification/dataset/test_images'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T08:42:48.716640Z","iopub.execute_input":"2026-02-06T08:42:48.717021Z","iopub.status.idle":"2026-02-06T08:42:48.721094Z","shell.execute_reply.started":"2026-02-06T08:42:48.716996Z","shell.execute_reply":"2026-02-06T08:42:48.720417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(location.train_csv)\nval_df = pd.read_csv(location.val_csv)\ntest_df = pd.read_csv(location.test_csv)\n\ntrain_df['id'] = train_df['id'].apply(lambda x: os.path.join(location.train_img,x))\nval_df['id'] = val_df['id'].apply(lambda x: os.path.join(location.val_img,x))\ntest_df['id'] = test_df['id'].apply(lambda x: os.path.join(location.test_img,x))\n\nall_df = pd.concat([train_df,val_df],axis=0)\n\nall_df.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T08:42:48.721948Z","iopub.execute_input":"2026-02-06T08:42:48.722198Z","iopub.status.idle":"2026-02-06T08:42:48.777272Z","shell.execute_reply.started":"2026-02-06T08:42:48.722175Z","shell.execute_reply":"2026-02-06T08:42:48.776581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig,ax = plt.subplots(1,2)\nsample = all_df.groupby('label').sample(1)\n\nfor idx,(_,row) in enumerate(sample.iterrows()):\n    img_path = row['id']\n    label = row['label']\n\n    img = Image.open(img_path).convert(\"RGB\")\n\n    ax[idx].imshow(img)\n    ax[idx].axis('off')\n    ax[idx].set_title(f'{label}')\n\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T08:42:48.778106Z","iopub.execute_input":"2026-02-06T08:42:48.778618Z","iopub.status.idle":"2026-02-06T08:42:49.081761Z","shell.execute_reply.started":"2026-02-06T08:42:48.778595Z","shell.execute_reply":"2026-02-06T08:42:49.080970Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AnimeCharacters(Dataset):\n    def __init__(self,df,tfms=None,test=None):\n        self.df = df\n        self.tfms = tfms\n        self.test = test\n    def __len__(self):\n        return len(self.df)\n    def __getitem__(self,idx):\n        row = self.df.iloc[idx]\n\n        img_path = row['id']\n        \n        if self.test is None:\n            label = row['label']\n\n\n        img = Image.open(img_path).convert(\"RGB\")\n        \n        if self.tfms:\n            img = self.tfms(img)\n\n\n        if self.test:\n            return {\n                'feature': img,\n                # 'label':torch.tensor(label,dtype=torch.long),\n                'metadata': img_path.split('/')[-1]\n            }\n        return {\n                'feature': img,\n                'label':torch.tensor(label,dtype=torch.long),\n                # 'metadata': img_path.strip('/')[-1]\n        }\n\n\ndef get_loaders(df,splitter,transforms:list,batches:list):\n\n    loaders = []\n\n    for tr_idx,val_idx in splitter.split(np.zeros(len(df)),df['label']):\n        train = df.iloc[tr_idx]\n        val = df.iloc[val_idx]\n\n        train_loader = DataLoader(\n            AnimeCharacters(train,transforms[0]),\n            shuffle=True,\n            batch_size=batches[0],\n            pin_memory=True,\n            num_workers=0\n        )\n        val_loader = DataLoader(\n            AnimeCharacters(val,transforms[1]),\n            shuffle=False,\n            batch_size=batches[1]\n        )\n\n        loaders.append((train_loader,val_loader))\n\n    return loaders","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T08:42:49.083491Z","iopub.execute_input":"2026-02-06T08:42:49.083758Z","iopub.status.idle":"2026-02-06T08:42:49.090938Z","shell.execute_reply.started":"2026-02-06T08:42:49.083735Z","shell.execute_reply":"2026-02-06T08:42:49.090266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tfms = transforms.Compose([\n    transforms.RandomResizedCrop(\n        224, scale=(0.85, 1.0), ratio=(0.9, 1.1)\n    ),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomRotation(12),\n\n    transforms.ColorJitter(\n        brightness=0.15,\n        contrast=0.15,\n        saturation=0.08,\n        hue=0.02\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.RandomErasing(\n        p=0.25,\n        scale=(0.02, 0.12),\n        ratio=(0.3, 3.3),\n        value=\"random\"\n    ),\n\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std =[0.229, 0.224, 0.225]\n    ),\n])\n\nval_tfms = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std =[0.229, 0.224, 0.225]\n    ),\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T10:01:23.307154Z","iopub.execute_input":"2026-02-06T10:01:23.307797Z","iopub.status.idle":"2026-02-06T10:01:23.313984Z","shell.execute_reply.started":"2026-02-06T10:01:23.307763Z","shell.execute_reply":"2026-02-06T10:01:23.313309Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"splitter = StratifiedKFold(n_splits=15,shuffle=True,random_state=42)\nall_loaders = get_loaders(all_df,splitter,[train_tfms,val_tfms],[32,64])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T10:01:37.780521Z","iopub.execute_input":"2026-02-06T10:01:37.780786Z","iopub.status.idle":"2026-02-06T10:01:37.796592Z","shell.execute_reply.started":"2026-02-06T10:01:37.780765Z","shell.execute_reply":"2026-02-06T10:01:37.795827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models import resnet101, ResNet101_Weights\n\ndef get_model():\n    model = resnet101(weights=ResNet101_Weights.DEFAULT)\n    input_feats = model.fc.in_features\n\n    model.fc= nn.Sequential(\n        nn.Linear(input_feats,512),\n        nn.GELU(),\n        nn.Dropout(0.25),\n        nn.Linear(512,2)\n    )\n\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T10:01:47.647028Z","iopub.execute_input":"2026-02-06T10:01:47.647649Z","iopub.status.idle":"2026-02-06T10:01:47.651938Z","shell.execute_reply.started":"2026-02-06T10:01:47.647621Z","shell.execute_reply":"2026-02-06T10:01:47.651218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from transformers import get_cosine_schedule_with_warmup\nfrom tqdm.notebook import tqdm,trange\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nEPOCHS = 20\nPATIENCE = 3\nLR = 3e-4\nWARMUP_RATIO = 0.04\n\nfor fold_idx, loaders in (enumerate(tqdm(all_loaders))):\n    print(f\"\\n Training Fold {fold_idx+1}\")\n\n    train_loader, val_loader = loaders\n    \n    model = get_model().to(device)\n\n    for param in model.conv1.parameters():\n        param.requires_grad = False\n    for param in model.bn1.parameters():\n        param.requires_grad = False\n    \n    param_groups = [\n        {\"params\": model.layer1.parameters(), \"lr\": LR * 0.1},\n        {\"params\": model.layer2.parameters(), \"lr\": LR * 0.3},\n        {\"params\": model.layer3.parameters(), \"lr\": LR * 0.5},\n        {\"params\": model.layer4.parameters(), \"lr\": LR * 0.7},\n        {\"params\": model.fc.parameters(),     \"lr\": LR},\n    ]\n    \n    optimizer = torch.optim.AdamW(\n        param_groups,\n        weight_decay=1e-4\n    )\n    \n        \n    # optimizer = torch.optim.Adam(model.parameters(), lr=LR)\n    criterion = nn.CrossEntropyLoss(label_smoothing=0.01)\n\n    total_steps = EPOCHS * len(train_loader)\n    warmup_steps = int(WARMUP_RATIO * total_steps)\n\n    scheduler = get_cosine_schedule_with_warmup(\n        optimizer,\n        num_warmup_steps=warmup_steps,\n        num_training_steps=total_steps\n    )\n\n    best_val_loss = float(\"inf\")\n    patience_counter = 0\n\n    history = {\n        \"train_loss\": [],\n        \"val_loss\": [],\n        \"val_acc\": []\n    }\n\n    for epoch in range(EPOCHS):\n        model.train()\n        train_losses = []\n\n        pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}\", leave=False)\n\n        for batch in pbar:\n            images = batch[\"feature\"].to(device)\n            labels = batch[\"label\"].to(device)\n\n            optimizer.zero_grad()\n\n            logits = model(images)\n            loss = criterion(logits, labels)\n\n            loss.backward()\n            optimizer.step()\n            scheduler.step()\n\n            train_losses.append(loss.item())\n            pbar.set_postfix(train_loss=np.mean(train_losses))\n\n        avg_train_loss = np.mean(train_losses)\n\n        model.eval()\n        val_losses = []\n        correct = 0\n        total = 0\n\n        with torch.no_grad():\n            for batch in val_loader:\n                images = batch[\"feature\"].to(device)\n                labels = batch[\"label\"].to(device)\n\n                logits = model(images)\n                loss = criterion(logits, labels)\n\n                val_losses.append(loss.item())\n\n                preds = logits.argmax(dim=1)\n                correct += (preds == labels).sum().item()\n                total += labels.size(0)\n\n        avg_val_loss = np.mean(val_losses)\n        val_acc = correct / total\n\n        history[\"train_loss\"].append(avg_train_loss)\n        history[\"val_loss\"].append(avg_val_loss)\n        history[\"val_acc\"].append(val_acc)\n\n        print(\n            f\"Epoch [{epoch+1}/{EPOCHS}] | \"\n            f\"Train Loss: {avg_train_loss:.4f} | \"\n            f\"Val Loss: {avg_val_loss:.4f} | \"\n            f\"Val Acc: {val_acc:.4f}\"\n        )\n\n        if avg_val_loss < best_val_loss:\n            best_val_loss = avg_val_loss\n            patience_counter = 0\n\n            torch.save(\n                model.state_dict(),\n                f\"best_model_fold_{fold_idx}.pt\"\n            )\n        else:\n            patience_counter += 1\n            if patience_counter >= PATIENCE:\n                print(\"Early stopping triggered.\")\n                del model, optimizer\n                torch.cuda.empty_cache()\n                break\n\n    print(f\"Fold {fold_idx+1} finished.\\n\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T10:02:15.060114Z","iopub.execute_input":"2026-02-06T10:02:15.060603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\nimport torch.nn.functional as F\n\ntest_loader = DataLoader(AnimeCharacters(test_df,val_tfms,test=True),batch_size=32,shuffle=False)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel_paths = sorted(glob.glob(\"best_model_fold_*.pt\"))\nprint(\"Loaded models:\", model_paths)\n\nmodels = []\n\nfor path in model_paths:\n    model = get_model().to(device)\n    model.load_state_dict(torch.load(path, map_location=device))\n    torch.cuda.empty_cache()\n    model.eval()\n    models.append(model)\n\nnum_models = len(models)\nassert num_models > 0, \"No models found for ensembling.\"\n\n\nall_ids = []\nall_preds = []\nall_probs = []\n\n@torch.no_grad()\ndef ensemble_predict(test_loader, models):\n    for batch in tqdm(test_loader):\n        images = batch[\"feature\"].to(device)\n        ids = batch[\"metadata\"]\n\n        # Sum probabilities from each model\n        prob_sum = None\n\n        for model in models:\n            logits = model(images)\n            probs = F.softmax(logits, dim=1)\n\n            if prob_sum is None:\n                prob_sum = probs\n            else:\n                prob_sum += probs\n\n        avg_probs = prob_sum / len(models)\n        preds = avg_probs.argmax(dim=1)\n\n        all_ids.extend(ids)\n        all_preds.extend(preds.cpu())\n        all_probs.extend(avg_probs.cpu().numpy())\n\nensemble_predict(test_loader, models)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T09:59:28.477121Z","iopub.execute_input":"2026-02-06T09:59:28.477529Z","iopub.status.idle":"2026-02-06T09:59:44.100879Z","shell.execute_reply.started":"2026-02-06T09:59:28.477498Z","shell.execute_reply":"2026-02-06T09:59:44.100162Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds = [preds.item() for preds in all_preds]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T09:59:44.101802Z","iopub.execute_input":"2026-02-06T09:59:44.102051Z","iopub.status.idle":"2026-02-06T09:59:44.106529Z","shell.execute_reply.started":"2026-02-06T09:59:44.102029Z","shell.execute_reply":"2026-02-06T09:59:44.105819Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data = {'id':all_ids,'label':preds}\ndf = pd.DataFrame(data)\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T09:59:44.107389Z","iopub.execute_input":"2026-02-06T09:59:44.108042Z","iopub.status.idle":"2026-02-06T09:59:44.123023Z","shell.execute_reply.started":"2026-02-06T09:59:44.108012Z","shell.execute_reply":"2026-02-06T09:59:44.122469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.to_csv('submission.csv',index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-06T09:59:44.123791Z","iopub.execute_input":"2026-02-06T09:59:44.124048Z","iopub.status.idle":"2026-02-06T09:59:44.135616Z","shell.execute_reply.started":"2026-02-06T09:59:44.124022Z","shell.execute_reply":"2026-02-06T09:59:44.134932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}