{"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":[{"sourceType":"competition","sourceId":127713,"databundleVersionId":15291341}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import matplotlib.pyplot as plt\n%matplotlib inline\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torchvision.transforms import transforms\nfrom torch.utils.data import Dataset,DataLoader\nfrom PIL import Image\nimport os\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-03-28T12:48:20.055428Z","iopub.execute_input":"2026-03-28T12:48:20.056329Z","iopub.status.idle":"2026-03-28T12:48:20.063601Z","shell.execute_reply.started":"2026-03-28T12:48:20.056284Z","shell.execute_reply":"2026-03-28T12:48:20.062931Z"}},"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    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-03-28T12:48:20.064668Z","iopub.execute_input":"2026-03-28T12:48:20.064943Z","iopub.status.idle":"2026-03-28T12:48:20.074339Z","shell.execute_reply.started":"2026-03-28T12:48:20.064916Z","shell.execute_reply":"2026-03-28T12:48:20.073593Z"}},"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)\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))\nall_df = pd.concat([train_df,val_df],axis=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.075784Z","iopub.execute_input":"2026-03-28T12:48:20.076296Z","iopub.status.idle":"2026-03-28T12:48:20.109833Z","shell.execute_reply.started":"2026-03-28T12:48:20.076271Z","shell.execute_reply":"2026-03-28T12:48:20.109322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig,ax = plt.subplots(1,2)\nsample = all_df.groupby('label').sample(1)\nfor idx,(_,row) in enumerate(sample.iterrows()):\n    img_path = row['id']\n    label = row['label']\n    img = Image.open(img_path).convert(\"RGB\")\n    ax[idx].imshow(img)\n    ax[idx].axis('off')\n    ax[idx].set_title(f'{label}')\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.110641Z","iopub.execute_input":"2026-03-28T12:48:20.110922Z","iopub.status.idle":"2026-03-28T12:48:20.345339Z","shell.execute_reply.started":"2026-03-28T12:48:20.110890Z","shell.execute_reply":"2026-03-28T12:48:20.344568Z"}},"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                \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        loaders.append((train_loader,val_loader))\n    return loaders","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.347199Z","iopub.execute_input":"2026-03-28T12:48:20.347518Z","iopub.status.idle":"2026-03-28T12:48:20.355265Z","shell.execute_reply.started":"2026-03-28T12:48:20.347496Z","shell.execute_reply":"2026-03-28T12:48:20.354493Z"}},"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.RandomRotation(12),\n\n    transforms.ColorJitter(\n        brightness=0.10,\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    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std =[0.229, 0.224, 0.225]\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])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.356247Z","iopub.execute_input":"2026-03-28T12:48:20.356919Z","iopub.status.idle":"2026-03-28T12:48:20.369645Z","shell.execute_reply.started":"2026-03-28T12:48:20.356870Z","shell.execute_reply":"2026-03-28T12:48:20.369130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"splitter = StratifiedKFold(n_splits=10,shuffle=True,random_state=42)\n\n\nall_loaders = get_loaders(all_df,splitter,[train_tfms,val_tfms],[32,64])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.370383Z","iopub.execute_input":"2026-03-28T12:48:20.370626Z","iopub.status.idle":"2026-03-28T12:48:20.394482Z","shell.execute_reply.started":"2026-03-28T12:48:20.370605Z","shell.execute_reply":"2026-03-28T12:48:20.393899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models import resnet50, ResNet50_Weights\ndef get_model():\n    model = resnet50(weights=ResNet50_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.2),\n        nn.Linear(512,2)\n    )\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.395316Z","iopub.execute_input":"2026-03-28T12:48:20.395529Z","iopub.status.idle":"2026-03-28T12:48:20.400258Z","shell.execute_reply.started":"2026-03-28T12:48:20.395508Z","shell.execute_reply":"2026-03-28T12:48:20.399494Z"}},"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 = 5\nPATIENCE = 3\nLR = 1e-4\nWARMUP_RATIO = 0.04\n\nfor fold_idx, loaders in (enumerate(tqdm(all_loaders))):\n    print(f\"\\nTraining Fold {fold_idx+1}\")\n\n    train_loader, val_loader = loaders\n    \n    model = get_model().to(device)\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    best_val_loss = float(\"inf\")\n    patience_counter = 0\n\n    history = {\n        \"train_loss\": [],\n        \"val_loss\": [],\n        \"val_acc\": []\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\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T12:48:20.401219Z","iopub.execute_input":"2026-03-28T12:48:20.401712Z","iopub.status.idle":"2026-03-28T13:58:02.940855Z","shell.execute_reply.started":"2026-03-28T12:48:20.401689Z","shell.execute_reply":"2026-03-28T13:58:02.939667Z"},"scrolled":true},"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)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel_paths = sorted(glob.glob(\"best_model_fold_*.pt\"))\nprint(\"Loaded models:\", model_paths)\n\nmodels = []\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 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())\nensemble_predict(test_loader, models)\n\npreds = [preds.item() for preds in all_preds]\ndata = {'id':all_ids,'label':preds}\ndf = pd.DataFrame(data)\ndf.head()\ndf.to_csv('submission.csv',index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T13:58:41.468454Z","iopub.execute_input":"2026-03-28T13:58:41.468693Z","iopub.status.idle":"2026-03-28T13:58:58.862178Z","shell.execute_reply.started":"2026-03-28T13:58:41.468672Z","shell.execute_reply":"2026-03-28T13:58:58.861509Z"}},"outputs":[],"execution_count":null}]}