{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":21154,"databundleVersionId":1243559}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:16.987036Z","iopub.execute_input":"2026-05-03T06:31:16.987318Z","iopub.status.idle":"2026-05-03T06:31:18.032385Z","shell.execute_reply.started":"2026-05-03T06:31:16.987295Z","shell.execute_reply":"2026-05-03T06:31:18.031627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ls /kaggle/input/competitions/tpu-getting-started","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:18.033496Z","iopub.execute_input":"2026-05-03T06:31:18.033923Z","iopub.status.idle":"2026-05-03T06:31:18.158889Z","shell.execute_reply.started":"2026-05-03T06:31:18.033889Z","shell.execute_reply":"2026-05-03T06:31:18.158026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Config\nIMAGE_SIZE = [224, 224]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:18.161443Z","iopub.execute_input":"2026-05-03T06:31:18.161859Z","iopub.status.idle":"2026-05-03T06:31:18.166797Z","shell.execute_reply.started":"2026-05-03T06:31:18.161820Z","shell.execute_reply":"2026-05-03T06:31:18.165994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport io\nimport glob\nimport struct\nfrom multiprocessing import Pool, cpu_count\nfrom PIL import Image\nfrom tqdm import tqdm\n\nfrom tensorflow.core.example.example_pb2 import Example\n\n\n# ---- TFRecord Reader ----\ndef read_tfrecord(file_path):\n    with open(file_path, \"rb\") as f:\n        while True:\n            length_bytes = f.read(8)\n            if not length_bytes:\n                break\n\n            length = struct.unpack(\"<Q\", length_bytes)[0]\n            f.read(4)  # crc\n            data = f.read(length)\n            f.read(4)  # crc\n\n            yield data\n\n\n# ---- Parser ----\ndef parse_example(serialized):\n    example = Example()\n    example.ParseFromString(serialized)\n\n    features = example.features.feature\n\n    image = features[\"image\"].bytes_list.value[0]\n    label = features[\"class\"].int64_list.value[0]\n    img_id = features[\"id\"].bytes_list.value[0].decode()\n\n    return image, label, img_id\n\n\n# ---- Worker ----\ndef process_file(file_path):\n    local_count = 0\n\n    for record in read_tfrecord(file_path):\n        img_bytes, label, img_id = parse_example(record)\n\n        class_dir = os.path.join(OUTPUT_PATH, str(label))\n        os.makedirs(class_dir, exist_ok=True)\n\n        img = Image.open(io.BytesIO(img_bytes))\n        img.save(os.path.join(class_dir, f\"{img_id}.jpg\"))\n\n        local_count += 1\n\n    return local_count","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:18.167805Z","iopub.execute_input":"2026-05-03T06:31:18.168126Z","iopub.status.idle":"2026-05-03T06:31:40.307303Z","shell.execute_reply.started":"2026-05-03T06:31:18.168105Z","shell.execute_reply":"2026-05-03T06:31:40.306707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/competitions/tpu-getting-started\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:40.308220Z","iopub.execute_input":"2026-05-03T06:31:40.308772Z","iopub.status.idle":"2026-05-03T06:31:40.312464Z","shell.execute_reply.started":"2026-05-03T06:31:40.308735Z","shell.execute_reply":"2026-05-03T06:31:40.311748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"OUTPUT_PATH = \"/kaggle/working/train_images\"\nos.makedirs(OUTPUT_PATH, exist_ok=True)\nINPUT_PATH = BASE_PATH + \"/tfrecords-jpeg-224x224/train\"\n# ---- Run Parallel ----\nfiles_train = glob.glob(INPUT_PATH + \"/*.tfrec\")\n\nnum_workers = min(8, cpu_count())  # Kaggle usually 2–4 CPUs\n\nwith Pool(num_workers) as p:\n    results = list(tqdm(p.imap(process_file, files_train), total=len(files_train)))\n\nprint(f\"Total train images processed: {sum(results)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:40.313442Z","iopub.execute_input":"2026-05-03T06:31:40.313787Z","iopub.status.idle":"2026-05-03T06:31:46.378355Z","shell.execute_reply.started":"2026-05-03T06:31:40.313753Z","shell.execute_reply":"2026-05-03T06:31:46.377238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"OUTPUT_PATH = \"/kaggle/working/val_images\"\nos.makedirs(OUTPUT_PATH, exist_ok=True)\nINPUT_PATH = BASE_PATH + \"/tfrecords-jpeg-224x224/val\"\n# ---- Run Parallel ----\nfiles_val = glob.glob(INPUT_PATH + \"/*.tfrec\")\n\nnum_workers = min(8, cpu_count())  # Kaggle usually 2–4 CPUs\n\nwith Pool(num_workers) as p:\n    results = list(tqdm(p.imap(process_file, files_val), total=len(files_val)))\n\nprint(f\"Total validation images processed: {sum(results)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:46.379813Z","iopub.execute_input":"2026-05-03T06:31:46.380242Z","iopub.status.idle":"2026-05-03T06:31:48.081385Z","shell.execute_reply.started":"2026-05-03T06:31:46.380200Z","shell.execute_reply":"2026-05-03T06:31:48.080630Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.datasets import ImageFolder\nfrom torchvision import transforms\nfrom torch.utils.data import DataLoader\n\nIMAGE_SIZE = 224\n\ntransform = transforms.Compose([\n    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\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\ntrain_dataset = ImageFolder(\n    root=\"/kaggle/working/train_images\",\n    transform=transform\n)\n\nval_dataset = ImageFolder(\n    root=\"/kaggle/working/val_images\",\n    transform=transform\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:31:48.082697Z","iopub.execute_input":"2026-05-03T06:31:48.083282Z","iopub.status.idle":"2026-05-03T06:31:56.132949Z","shell.execute_reply.started":"2026-05-03T06:31:48.083254Z","shell.execute_reply":"2026-05-03T06:31:56.132320Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=64, # optimise it 64-128\n    shuffle=True,\n    num_workers=2, # optimise it 2-4\n    pin_memory=True,\n    persistent_workers=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=64, \n    shuffle=False,\n    num_workers=2,\n    pin_memory=True,\n    persistent_workers=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:34:07.194452Z","iopub.execute_input":"2026-05-03T06:34:07.195231Z","iopub.status.idle":"2026-05-03T06:34:07.203285Z","shell.execute_reply.started":"2026-05-03T06:34:07.195200Z","shell.execute_reply":"2026-05-03T06:34:07.202608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, labels = next(iter(train_loader))\nprint(images.shape, labels.shape)\n\nimages, labels = next(iter(val_loader))\nprint(images.shape, labels.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:34:11.300522Z","iopub.execute_input":"2026-05-03T06:34:11.301477Z","iopub.status.idle":"2026-05-03T06:34:11.948600Z","shell.execute_reply.started":"2026-05-03T06:34:11.301433Z","shell.execute_reply":"2026-05-03T06:34:11.947334Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import timm\nimport torch\nimport torch.nn as nn\n\nNUM_CLASSES = 104  # flower classes\n\ndef build_model(model_name=\"convnext_tiny\", pretrained=True):\n    model = timm.create_model(\n        model_name,\n        pretrained=pretrained,\n        num_classes=NUM_CLASSES\n    )\n    return model\n\nmodel_convnext_tiny = build_model(\"convnext_tiny\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:34:26.734936Z","iopub.execute_input":"2026-05-03T06:34:26.735875Z","iopub.status.idle":"2026-05-03T06:34:27.308271Z","shell.execute_reply.started":"2026-05-03T06:34:26.735837Z","shell.execute_reply":"2026-05-03T06:34:27.307663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel_convnext_tiny = model_convnext_tiny.to(device)\n\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\noptimizer = torch.optim.AdamW(\n    model_convnext_tiny.parameters(),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:34:29.859039Z","iopub.execute_input":"2026-05-03T06:34:29.859744Z","iopub.status.idle":"2026-05-03T06:34:29.904272Z","shell.execute_reply.started":"2026-05-03T06:34:29.859713Z","shell.execute_reply":"2026-05-03T06:34:29.903714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scaler = torch.cuda.amp.GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:34:34.505275Z","iopub.execute_input":"2026-05-03T06:34:34.506039Z","iopub.status.idle":"2026-05-03T06:34:34.510068Z","shell.execute_reply.started":"2026-05-03T06:34:34.506008Z","shell.execute_reply":"2026-05-03T06:34:34.509291Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm import tqdm\n\ndef train_one_epoch(model, loader, optimizer, scaler):\n    model.train()\n    total_loss = 0\n\n    for images, labels in tqdm(loader):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n\n        with torch.cuda.amp.autocast():\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        total_loss += loss.item()\n\n    return total_loss / len(loader)\n\ndef validate(model, loader):\n    model.eval()\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in loader:\n            images, labels = images.to(device), labels.to(device)\n\n            outputs = model(images)\n            preds = outputs.argmax(dim=1)\n\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n    return correct / total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:32:04.753831Z","iopub.execute_input":"2026-05-03T06:32:04.754130Z","iopub.status.idle":"2026-05-03T06:32:04.764985Z","shell.execute_reply.started":"2026-05-03T06:32:04.754098Z","shell.execute_reply":"2026-05-03T06:32:04.764128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 16\n\nbest_acc = 0\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(model_convnext_tiny, train_loader, optimizer, scaler)\n    val_acc = validate(model_convnext_tiny, val_loader)\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1} | Loss: {train_loss:.4f} | Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model_convnext_tiny.state_dict(), \"best_model_convnext_tiny.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T06:34:40.910810Z","iopub.execute_input":"2026-05-03T06:34:40.911627Z","iopub.status.idle":"2026-05-03T07:04:41.436566Z","shell.execute_reply.started":"2026-05-03T06:34:40.911596Z","shell.execute_reply":"2026-05-03T07:04:41.435788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_convnext_tiny = build_model(\"convnext_tiny\", pretrained=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:07:28.294920Z","iopub.execute_input":"2026-05-03T07:07:28.295643Z","iopub.status.idle":"2026-05-03T07:07:28.700995Z","shell.execute_reply.started":"2026-05-03T07:07:28.295611Z","shell.execute_reply":"2026-05-03T07:07:28.700298Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_convnext_tiny.load_state_dict(torch.load(\"/kaggle/working/best_model_convnext_tiny.pth\", map_location=device))\n\nmodel_convnext_tiny.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:08:14.595637Z","iopub.execute_input":"2026-05-03T07:08:14.596296Z","iopub.status.idle":"2026-05-03T07:08:14.817308Z","shell.execute_reply.started":"2026-05-03T07:08:14.596264Z","shell.execute_reply":"2026-05-03T07:08:14.816664Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport os\n\nTEST_OUTPUT_PATH = \"/kaggle/working/test_images\"\nos.makedirs(TEST_OUTPUT_PATH, exist_ok=True)\n\nfrom tensorflow.core.example.example_pb2 import Example\n\ndef parse_test_example(serialized):\n    example = Example()\n    example.ParseFromString(serialized)\n\n    features = example.features.feature\n\n    image = features[\"image\"].bytes_list.value[0]\n    img_id = features[\"id\"].bytes_list.value[0].decode()\n\n    return image, img_id\n\ndef process_test_file(file_path):\n    count = 0\n\n    for record in read_tfrecord(file_path):\n        img_bytes, img_id = parse_test_example(record)\n\n        # fast decode\n        img_array = np.frombuffer(img_bytes, np.uint8)\n        img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)\n\n        cv2.imwrite(os.path.join(TEST_OUTPUT_PATH, f\"{img_id}.jpg\"), img)\n\n        count += 1\n\n    return count","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:14:49.760035Z","iopub.execute_input":"2026-05-03T07:14:49.760854Z","iopub.status.idle":"2026-05-03T07:14:49.767764Z","shell.execute_reply.started":"2026-05-03T07:14:49.760822Z","shell.execute_reply":"2026-05-03T07:14:49.766988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEST_INPUT_PATH = BASE_PATH + \"/tfrecords-jpeg-224x224/test\"\n\nfiles_test = glob.glob(TEST_INPUT_PATH + \"/*.tfrec\")\n\nnum_workers = min(4, cpu_count())  # safer on Kaggle\n\nwith Pool(num_workers) as p:\n    results = list(tqdm(p.imap(process_test_file, files_test), total=len(files_test)))\n\nprint(f\"Total test images processed: {sum(results)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:14:53.733046Z","iopub.execute_input":"2026-05-03T07:14:53.733787Z","iopub.status.idle":"2026-05-03T07:14:57.279322Z","shell.execute_reply.started":"2026-05-03T07:14:53.733757Z","shell.execute_reply":"2026-05-03T07:14:57.278215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset\nfrom PIL import Image\nimport os\n\nclass TestDataset(Dataset):\n    def __init__(self, folder, transform=None):\n        self.paths = sorted([\n            os.path.join(folder, f)\n            for f in os.listdir(folder)\n            if f.endswith(\".jpg\")\n        ])\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        path = self.paths[idx]\n        img = Image.open(path).convert(\"RGB\")\n\n        if self.transform:\n            img = self.transform(img)\n\n        img_id = os.path.basename(path).replace(\".jpg\", \"\")\n        return img, img_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:18:36.372903Z","iopub.execute_input":"2026-05-03T07:18:36.373478Z","iopub.status.idle":"2026-05-03T07:18:36.379590Z","shell.execute_reply.started":"2026-05-03T07:18:36.373448Z","shell.execute_reply":"2026-05-03T07:18:36.378899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.datasets import ImageFolder\nfrom torch.utils.data import DataLoader\n\ntest_dataset = TestDataset(\n    \"/kaggle/working/test_images\",\n    transform=transform\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=64,\n    shuffle=False,\n    num_workers=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:18:55.701355Z","iopub.execute_input":"2026-05-03T07:18:55.702173Z","iopub.status.idle":"2026-05-03T07:19:26.824144Z","shell.execute_reply.started":"2026-05-03T07:18:55.702139Z","shell.execute_reply":"2026-05-03T07:19:26.822961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ids = []\npreds = []\n\nmodel_convnext_tiny.eval()\n\nwith torch.no_grad():\n    for images, img_ids in test_loader:\n        images = images.to(device)\n\n        outputs = model_convnext_tiny(images)\n        pred = outputs.argmax(dim=1).cpu().numpy()\n\n        ids.extend(img_ids)\n        preds.extend(pred)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:20:08.248602Z","iopub.execute_input":"2026-05-03T07:20:08.249159Z","iopub.status.idle":"2026-05-03T07:20:38.560324Z","shell.execute_reply.started":"2026-05-03T07:20:08.249125Z","shell.execute_reply":"2026-05-03T07:20:38.559158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.DataFrame({\n    \"id\": ids,\n    \"label\": preds\n})\n\ndf.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:21:42.588092Z","iopub.execute_input":"2026-05-03T07:21:42.588598Z","iopub.status.idle":"2026-05-03T07:21:42.620627Z","shell.execute_reply.started":"2026-05-03T07:21:42.588524Z","shell.execute_reply":"2026-05-03T07:21:42.619954Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\n\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(IMAGE_SIZE, scale=(0.7, 1.0)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.3),  # flowers → vertical flip is useful\n    transforms.RandomRotation(20),\n    \n    transforms.ColorJitter(\n        brightness=0.2,\n        contrast=0.2,\n        saturation=0.2,\n        hue=0.05\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n\n    transforms.RandomErasing(p=0.25)\n])\n\ntrain_dataset = ImageFolder(\n    root=\"/kaggle/working/train_images\",\n    transform=train_transform\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=64, # optimise it 64-128\n    shuffle=True,\n    num_workers=1, # optimise it 2-4\n    pin_memory=True,\n    persistent_workers=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:39:51.448214Z","iopub.execute_input":"2026-05-03T07:39:51.448695Z","iopub.status.idle":"2026-05-03T07:39:51.935997Z","shell.execute_reply.started":"2026-05-03T07:39:51.448664Z","shell.execute_reply":"2026-05-03T07:39:51.935164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport timm\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nNUM_CLASSES = len(train_dataset.classes)\n\nmodel_convnext_tiny = timm.create_model(\n    \"convnext_tiny\",\n    pretrained=False,   # important when loading weights\n    num_classes=NUM_CLASSES\n)\n\nmodel_convnext_tiny.load_state_dict(torch.load(\n    \"/kaggle/working/best_model_convnext_tiny.pth\",\n    map_location=device\n))\n\nmodel_convnext_tiny.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:40:02.998884Z","iopub.execute_input":"2026-05-03T07:40:02.999818Z","iopub.status.idle":"2026-05-03T07:40:03.604881Z","shell.execute_reply.started":"2026-05-03T07:40:02.999789Z","shell.execute_reply":"2026-05-03T07:40:03.604182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\noptimizer = torch.optim.AdamW(\n    model_convnext_tiny.parameters(),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)\n\nscaler = torch.cuda.amp.GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:40:12.657412Z","iopub.execute_input":"2026-05-03T07:40:12.657922Z","iopub.status.idle":"2026-05-03T07:40:12.663351Z","shell.execute_reply.started":"2026-05-03T07:40:12.657895Z","shell.execute_reply":"2026-05-03T07:40:12.662504Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_loader = DataLoader(\n    val_dataset,\n    batch_size=64, \n    shuffle=False,\n    num_workers=2,\n    pin_memory=True,\n    persistent_workers=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:33:38.226965Z","iopub.execute_input":"2026-05-03T09:33:38.227380Z","iopub.status.idle":"2026-05-03T09:33:38.231757Z","shell.execute_reply.started":"2026-05-03T09:33:38.227350Z","shell.execute_reply":"2026-05-03T09:33:38.230937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nbest_acc = 0\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(model_convnext_tiny, train_loader, optimizer, scaler)\n    val_acc = validate(model_convnext_tiny, val_loader)\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1} | Loss: {train_loss:.4f} | Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model_convnext_tiny.state_dict(), \"best_model_convnext_tiny_aug.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T07:43:32.954019Z","iopub.execute_input":"2026-05-03T07:43:32.954631Z","iopub.status.idle":"2026-05-03T08:03:48.645885Z","shell.execute_reply.started":"2026-05-03T07:43:32.954591Z","shell.execute_reply":"2026-05-03T08:03:48.644757Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(5):\n    train_loss = train_one_epoch(model_convnext_tiny, train_loader, optimizer, scaler)\n    val_acc = validate(model_convnext_tiny, val_loader)\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1} | Loss: {train_loss:.4f} | Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model_convnext_tiny.state_dict(), \"best_model_convnext_tiny_aug.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T08:05:28.239777Z","iopub.execute_input":"2026-05-03T08:05:28.240304Z","iopub.status.idle":"2026-05-03T08:15:29.459244Z","shell.execute_reply.started":"2026-05-03T08:05:28.240260Z","shell.execute_reply":"2026-05-03T08:15:29.458341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_efficient_b0 = build_model(\"efficientnet_b0\")\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel_efficient_b0 = model_efficient_b0.to(device)\n\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\noptimizer = torch.optim.AdamW(\n    model_efficient_b0.parameters(),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)\n\nscaler = torch.cuda.amp.GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T08:19:28.634059Z","iopub.execute_input":"2026-05-03T08:19:28.634727Z","iopub.status.idle":"2026-05-03T08:19:30.009694Z","shell.execute_reply.started":"2026-05-03T08:19:28.634695Z","shell.execute_reply":"2026-05-03T08:19:30.009090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\n\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(IMAGE_SIZE, scale=(0.7, 1.0)),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.3),  # flowers → vertical flip is useful\n    transforms.RandomRotation(15),\n    \n    transforms.ColorJitter(\n        brightness=0.2,\n        contrast=0.2,\n        saturation=0.2,\n        hue=0.05\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n\n    transforms.RandomErasing(p=0.25)\n])\n\ntrain_dataset = ImageFolder(\n    root=\"/kaggle/working/train_images\",\n    transform=train_transform\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=64, # optimise it 64-128\n    shuffle=True,\n    num_workers=2, # optimise it 2-4\n    pin_memory=True,\n    persistent_workers=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:33:32.288892Z","iopub.execute_input":"2026-05-03T09:33:32.289469Z","iopub.status.idle":"2026-05-03T09:33:32.325984Z","shell.execute_reply.started":"2026-05-03T09:33:32.289438Z","shell.execute_reply":"2026-05-03T09:33:32.325332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# freeze everything\nfor param in model_efficient_b0.parameters():\n    param.requires_grad = False\n\n# unfreeze classifier safely\nfor name, param in model_efficient_b0.named_parameters():\n    if \"classifier\" in name:\n        param.requires_grad = True\n\noptimizer = torch.optim.AdamW(\n    model_efficient_b0.classifier.parameters(),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nHEAD_EPOCHS = 2\n\nfor epoch in range(HEAD_EPOCHS):\n    train_loss = train_one_epoch(model_efficient_b0, train_loader, optimizer, scaler)\n    val_acc = validate(model_efficient_b0, val_loader)\n\n    print(f\"[HEAD] Epoch {epoch+1} | Loss: {train_loss:.4f} | Val: {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T08:24:11.132243Z","iopub.execute_input":"2026-05-03T08:24:11.133268Z","iopub.status.idle":"2026-05-03T08:27:53.528794Z","shell.execute_reply.started":"2026-05-03T08:24:11.133222Z","shell.execute_reply":"2026-05-03T08:27:53.528129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 15\n\nfor param in model_efficient_b0.parameters():\n    param.requires_grad = True\n\noptimizer = torch.optim.AdamW(\n    model_efficient_b0.parameters(),\n    lr=1e-4,   # 🔥 lower LR for fine-tuning\n    weight_decay=1e-4\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T08:27:58.034037Z","iopub.execute_input":"2026-05-03T08:27:58.034762Z","iopub.status.idle":"2026-05-03T08:27:58.041531Z","shell.execute_reply.started":"2026-05-03T08:27:58.034730Z","shell.execute_reply":"2026-05-03T08:27:58.040655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_acc = 0\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(model_efficient_b0, train_loader, optimizer, scaler)\n    val_acc = validate(model_efficient_b0, val_loader)\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1} | Loss: {train_loss:.4f} | Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model_efficient_b0.state_dict(), \"best_model_efficient_b0_aug.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T08:28:10.142233Z","iopub.execute_input":"2026-05-03T08:28:10.143004Z","iopub.status.idle":"2026-05-03T08:55:57.251733Z","shell.execute_reply.started":"2026-05-03T08:28:10.142961Z","shell.execute_reply":"2026-05-03T08:55:57.250952Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(10):\n    train_loss = train_one_epoch(model_efficient_b0, train_loader, optimizer, scaler)\n    val_acc = validate(model_efficient_b0, val_loader)\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1} | Loss: {train_loss:.4f} | Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model_efficient_b0.state_dict(), \"best_model_efficient_b0_aug.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T08:56:57.916719Z","iopub.execute_input":"2026-05-03T08:56:57.917431Z","iopub.status.idle":"2026-05-03T09:15:22.949498Z","shell.execute_reply.started":"2026-05-03T08:56:57.917398Z","shell.execute_reply":"2026-05-03T09:15:22.948823Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# MixUp and CutMix","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport torch\n\ndef mixup(images, labels, alpha=0.2):\n    lam = np.random.beta(alpha, alpha)\n    index = torch.randperm(images.size(0)).to(images.device)\n\n    mixed_images = lam * images + (1 - lam) * images[index]\n    labels_a, labels_b = labels, labels[index]\n\n    return mixed_images, labels_a, labels_b, lam","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:18:07.615405Z","iopub.execute_input":"2026-05-03T09:18:07.616218Z","iopub.status.idle":"2026-05-03T09:18:07.621031Z","shell.execute_reply.started":"2026-05-03T09:18:07.616187Z","shell.execute_reply":"2026-05-03T09:18:07.620240Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def cutmix(images, labels, alpha=1.0):\n    lam = np.random.beta(alpha, alpha)\n    batch_size, _, H, W = images.size()\n    index = torch.randperm(batch_size).to(images.device)\n\n    # bounding box\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = int(W * cut_rat)\n    cut_h = int(H * cut_rat)\n\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n\n    x1 = np.clip(cx - cut_w // 2, 0, W)\n    x2 = np.clip(cx + cut_w // 2, 0, W)\n    y1 = np.clip(cy - cut_h // 2, 0, H)\n    y2 = np.clip(cy + cut_h // 2, 0, H)\n\n    # apply cutmix\n    images[:, :, y1:y2, x1:x2] = images[index, :, y1:y2, x1:x2]\n\n    # adjust lambda\n    lam = 1 - ((x2 - x1) * (y2 - y1) / (W * H))\n\n    labels_a, labels_b = labels, labels[index]\n\n    return images, labels_a, labels_b, lam","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:18:10.990038Z","iopub.execute_input":"2026-05-03T09:18:10.990567Z","iopub.status.idle":"2026-05-03T09:18:10.999328Z","shell.execute_reply.started":"2026-05-03T09:18:10.990526Z","shell.execute_reply":"2026-05-03T09:18:10.998697Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\n\ndef mixup_cutmix(images, labels, mixup_prob=0.7):\n    if random.random() < mixup_prob:\n        return mixup(images, labels)\n    else:\n        return cutmix(images, labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:18:14.133978Z","iopub.execute_input":"2026-05-03T09:18:14.134911Z","iopub.status.idle":"2026-05-03T09:18:14.139267Z","shell.execute_reply.started":"2026-05-03T09:18:14.134877Z","shell.execute_reply":"2026-05-03T09:18:14.138580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch_mixup_cut_mix(model, loader, optimizer, scaler, model_ema=None):\n    model.train()\n    total_loss = 0\n\n    for images, labels in tqdm(loader):\n        images = images.to(device)\n        labels = labels.to(device)\n\n        # 🔥 Mixup/CutMix auto-switch\n        images, labels_a, labels_b, lam = mixup_cutmix(images, labels)\n\n        optimizer.zero_grad()\n\n        with torch.cuda.amp.autocast():\n            outputs = model(images)\n\n            loss = (\n                lam * criterion(outputs, labels_a) +\n                (1 - lam) * criterion(outputs, labels_b)\n            )\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        if model_ema:\n            model_ema.update(model)\n\n        total_loss += loss.item()\n\n    return total_loss / len(loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:30:38.189617Z","iopub.execute_input":"2026-05-03T09:30:38.190403Z","iopub.status.idle":"2026-05-03T09:30:38.196235Z","shell.execute_reply.started":"2026-05-03T09:30:38.190369Z","shell.execute_reply":"2026-05-03T09:30:38.195633Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_efficient_b0 = build_model(\"efficientnet_b0\")\n\nfrom timm.utils import ModelEma\n\nmodel_ema = ModelEma(\n    model_efficient_b0,\n    decay=0.999,    # smoothing factor\n    device=device   # keep on same device for speed\n)\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel_efficient_b0 = model_efficient_b0.to(device)\n\n# freeze everything\nfor param in model_efficient_b0.parameters():\n    param.requires_grad = False\n\n# unfreeze classifier safely\nfor name, param in model_efficient_b0.named_parameters():\n    if \"classifier\" in name:\n        param.requires_grad = True\n\noptimizer = torch.optim.AdamW(\n    model_efficient_b0.classifier.parameters(),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nHEAD_EPOCHS = 5\n\nfor epoch in range(HEAD_EPOCHS):\n    train_loss = train_one_epoch_mixup_cut_mix(model_efficient_b0, train_loader, optimizer, scaler, model_ema)\n    val_acc = validate(model_efficient_b0, val_loader)\n\n    print(f\"[HEAD] Epoch {epoch+1} | Loss: {train_loss:.4f} | Val: {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:19:19.608777Z","iopub.execute_input":"2026-05-03T09:19:19.609321Z","iopub.status.idle":"2026-05-03T09:28:30.370837Z","shell.execute_reply.started":"2026-05-03T09:19:19.609293Z","shell.execute_reply":"2026-05-03T09:28:30.370046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 50\n\nfor param in model_efficient_b0.parameters():\n    param.requires_grad = True\n\noptimizer = torch.optim.AdamW(\n    model_efficient_b0.parameters(),\n    lr=3e-4,\n    weight_decay=1e-4\n)\n\nfrom torch.optim.lr_scheduler import SequentialLR, LinearLR, CosineAnnealingLR\n\nwarmup_epochs = 3\n\nscheduler = SequentialLR(\n    optimizer,\n    schedulers=[\n        LinearLR(optimizer, start_factor=0.1, total_iters=warmup_epochs),\n        CosineAnnealingLR(optimizer, T_max=EPOCHS - warmup_epochs)\n    ],\n    milestones=[warmup_epochs]\n)\n\nscaler = torch.cuda.amp.GradScaler()\n\nbest_acc = 0\nbest_acc_ema = 0\n\npatience = 10\nno_improve_epochs = 0\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch_mixup_cut_mix(\n        model_efficient_b0,\n        train_loader,\n        optimizer,\n        scaler,\n        model_ema\n    )\n\n    val_acc = validate(model_efficient_b0, val_loader)\n    val_acc_ema = validate(model_ema.ema, val_loader)  # ✅ fix\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1} | Loss: {train_loss:.4f} | Val: {val_acc:.4f}\")\n    print(f\"Epoch {epoch+1} | EMA Val: {val_acc_ema:.4f}\")\n\n    improved = False\n\n    # ---- Base model save ----\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model_efficient_b0.state_dict(), \"best_model_efficient_b0.pth\")\n\n    # ---- EMA model save (MAIN metric) ----\n    if val_acc_ema > best_acc_ema:\n        best_acc_ema = val_acc_ema\n        torch.save(model_ema.ema.state_dict(), \"best_model_efficient_b0_ema.pth\")\n        improved = True\n\n    # ---- Early stopping logic ----\n    if improved:\n        no_improve_epochs = 0\n    else:\n        no_improve_epochs += 1\n\n    if no_improve_epochs >= patience:\n        print(f\"Early stopping triggered at epoch {epoch+1}\")\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-03T09:36:48.411286Z","iopub.execute_input":"2026-05-03T09:36:48.412183Z","iopub.status.idle":"2026-05-03T10:20:08.884542Z","shell.execute_reply.started":"2026-05-03T09:36:48.412148Z","shell.execute_reply":"2026-05-03T10:20:08.883826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}