{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.7.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":29762,"databundleVersionId":2541532,"sourceType":"competition"},{"sourceId":11730964,"sourceType":"datasetVersion","datasetId":7364044},{"sourceId":11766825,"sourceType":"datasetVersion","datasetId":7374262}],"dockerImageVersionId":30124,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Import libraries and some directories ##","metadata":{}},{"cell_type":"code","source":"import pathlib\n\nimport torch\nimport torch.utils.data\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport numpy as np\nimport pandas as pd\n\nimport PIL.Image\nimport albumentations.pytorch\nimport cv2\nimport matplotlib.pyplot as plt\n\nfrom tqdm.notebook import tqdm\nfrom typing import List, Tuple\n\n\n\nMODEL_FILE = pathlib.Path('../input/google-landmark-2021-validation/model.pth')\nTRAIN_LABEL_FILE = pathlib.Path('train.csv')\nTRAIN_IMAGE_DIR = pathlib.Path('train')\nVALID_LABEL_FILE = pathlib.Path('val.csv')\nVALID_IMAGE_DIR = pathlib.Path('val')\nTEST_LABEL_FILE = pathlib.Path('test.csv')\nTEST_IMAGE_DIR = pathlib.Path('../input/landmark-recognition-2021/test')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Dataset(torch.utils.data.Dataset):\n    def __init__(self, label_file: pathlib.Path, image_dir: pathlib.Path) -> None:\n        super().__init__()\n        self.files = [\n            image_dir / n[0] / n[1] / n[2] / f'{n}.jpg'\n            for n in pd.read_csv(label_file)['id'].values]\n        \n        self.transformer = albumentations.Compose([\n            albumentations.SmallestMaxSize(IMAGE_SIZE, interpolation=cv2.INTER_CUBIC),\n            albumentations.CenterCrop(IMAGE_SIZE, IMAGE_SIZE),\n            albumentations.Normalize(),\n            albumentations.pytorch.ToTensorV2(),\n        ])\n\n    def __len__(self) -> int:\n        return len(self.files)\n\n    def __getitem__(self, index: int) -> Tuple[str, torch.Tensor]:\n        path = self.files[index]\n        image = PIL.Image.open(self.files[index])\n        image = self.transformer(image=np.array(image))['image']\n\n        return path.name[:-4], image","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Class and Functions for feature extraction","metadata":{}},{"cell_type":"markdown","source":"### Inference and Submission","metadata":{}},{"cell_type":"code","source":"from torchvision.models import resnet50\nimport torch.nn as nn","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Data Preprocessing ###","metadata":{}},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport os","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom sklearn.model_selection import train_test_split\nimport os\n\n# Config\nDATA_DIR = \"/kaggle/input/landmark-recognition-2021/train/\"\nTOP_K = 150  # Chọn nhiều hơn để đảm bảo sau kiểm tra vẫn đủ 100 class\nTARGET_CLASS_COUNT = 100\n\n# Step 1: Đọc dữ liệu gốc\ndf = pd.read_csv('/kaggle/input/landmark-recognition-2021/train.csv')\n\n# Step 2: Lọc top K landmark phổ biến (chưa kiểm tra file)\ntop_landmarks = (\n    df['landmark_id']\n    .value_counts()\n    .head(TOP_K)\n    .index\n)\ndf_top = df[df['landmark_id'].isin(top_landmarks)].copy()\n\n# Step 3: Kiểm tra file ảnh tồn tại\ndef image_exists(row):\n    img_id = row[\"id\"]\n    img_path = os.path.join(DATA_DIR, img_id[0], img_id[1], img_id[2], f\"{img_id}.jpg\")\n    return os.path.exists(img_path)\n\ndf_top = df_top[df_top.apply(image_exists, axis=1)].copy()\n\n# Step 4: Lọc lại đúng TARGET_CLASS_COUNT landmark còn đủ ảnh\nvalid_class_counts = df_top['landmark_id'].value_counts()\nfinal_landmarks = valid_class_counts.head(TARGET_CLASS_COUNT).index\nfiltered_df = df_top[df_top['landmark_id'].isin(final_landmarks)].copy()\n\nactual_class_count = filtered_df['landmark_id'].nunique()\nif actual_class_count < TARGET_CLASS_COUNT:\n    print(f\"Chỉ còn {actual_class_count} class sau khi kiểm tra ảnh. Cần tăng TOP_K hoặc lọc lại.\")\nelse:\n    print(f\"Đã giữ lại đúng {actual_class_count} class.\")\n\n# Step 5: Chia theo từng group landmark_id → train/val/test (80/10/10)\ntrain_list = []\nval_list = []\ntest_list = []\n\nfor landmark_id, group in filtered_df.groupby('landmark_id'):\n    if len(group) < 3:\n        continue  # skip nếu không đủ tách cả 3 tập\n\n    # 80% train, 20% temp\n    train_part, temp_part = train_test_split(\n        group, test_size=0.2, random_state=42, shuffle=True\n    )\n\n    # Tách tiếp 10% val, 10% test\n    if len(temp_part) >= 2:\n        val_part, test_part = train_test_split(\n            temp_part, test_size=0.5, random_state=42, shuffle=True\n        )\n    else:\n        val_part = temp_part\n        test_part = pd.DataFrame(columns=group.columns)\n\n    train_list.append(train_part)\n    val_list.append(val_part)\n    test_list.append(test_part)\n\n# Step 6: Gộp lại và ghi file\nfinal_train = pd.concat(train_list).reset_index(drop=True)\nfinal_val = pd.concat(val_list).reset_index(drop=True)\nfinal_test = pd.concat(test_list).reset_index(drop=True)\n\nfinal_train.to_csv(\"train.csv\", index=False)\nfinal_val.to_csv(\"val.csv\", index=False)\nfinal_test.to_csv(\"test.csv\", index=False)\n\nprint(f\"Train: {len(final_train)} samples, Val: {len(final_val)}, Test: {len(final_test)}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\nfrom tqdm import tqdm\n\ndef get_src_path(img_id):\n    return os.path.join(DATA_DIR, img_id[0], img_id[1], img_id[2], f\"{img_id}.jpg\")\n\ndef copy_images(image_ids, dest_dir):\n    os.makedirs(dest_dir, exist_ok=True)\n    for img_id in tqdm(image_ids):\n        src = get_src_path(img_id)\n        dst = os.path.join(dest_dir, f\"{img_id}.jpg\")\n        if not os.path.exists(dst):\n            try:\n                shutil.copyfile(src, dst)\n            except Exception as e:\n                print(f\"Lỗi copy {img_id}: {e}\")\n\n# Gọi:\ncopy_images(final_train['id'].unique(), \"/kaggle/working/train_images\")\ncopy_images(final_val['id'].unique(), \"/kaggle/working/val_images\")\ncopy_images(final_test['id'].unique(), \"/kaggle/working/test_images\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# Load your CSVs\n# train_df = pd.read_csv('/kaggle/working/train.csv')\n# test_df = pd.read_csv('/kaggle/working/test.csv')\n# val_df = pd.read_csv('/kaggle/working/val.csv')\ntrain_df = pd.read_csv('/kaggle/input/landmark/train.csv')\ntest_df = pd.read_csv('/kaggle/input/landmark/test.csv')\nval_df = pd.read_csv('/kaggle/input/landmark/val.csv')\n# Build mapping\nlandmark_id_to_idx = {lid: idx for idx, lid in enumerate(sorted(train_df['landmark_id'].unique()))}\nNUM_CLASSES = len(landmark_id_to_idx) \n\n# Map class_idx\ntrain_df['class_idx'] = train_df['landmark_id'].map(landmark_id_to_idx)\ntest_df['class_idx'] = test_df['landmark_id'].map(landmark_id_to_idx)\nval_df['class_idx'] = val_df['landmark_id'].map(landmark_id_to_idx)\n\nNUM_CLASSES = len(landmark_id_to_idx)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(NUM_CLASSES)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EDA","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom torch.utils.data import Dataset\nimport torchvision.transforms as T\n\nclass LandmarkDatasetEDA(Dataset):\n    def __init__(self, dataframe, data_dir, resize=True, image_size=224):\n        \"\"\"\n        Parameters:\n        - dataframe: pandas DataFrame, phải có cột 'id' và 'class_idx'\n        - data_dir: thư mục chứa ảnh (.jpg)\n        - resize: nếu True, resize về (image_size, image_size)\n        - image_size: kích thước đích nếu resize\n        \"\"\"\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.data_dir = data_dir\n        self.resize = resize\n        self.image_size = image_size\n\n        if self.resize:\n            self.transform = T.Compose([\n                T.Resize((image_size, image_size)),\n                T.ToTensor()\n            ])\n        else:\n            self.transform = None  # sẽ convert sang numpy array\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, idx):\n        row = self.dataframe.iloc[idx]\n        img_id = row[\"id\"]\n        class_idx = int(row[\"class_idx\"])\n\n        folder_path = os.path.join(\n            self.data_dir,\n            img_id[0],  # First character folder\n            img_id[1],  # Second character folder\n            img_id[2]   # Third character folder\n        )\n        \n        img_path = os.path.join(folder_path, f\"{img_id}.jpg\")\n        if not os.path.exists(img_path):\n            raise FileNotFoundError(f\"Image not found: {img_path}\")\n\n        image = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)  # Tensor (3, H, W)\n        else:\n            image = np.array(image)        # Numpy (H, W, 3)\n\n        return image, class_idx\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nfrom torch.utils.data import DataLoader\nDATA_DIR = \"/kaggle/input/landmark-recognition-2021/train/\"\ntrain_dataset = LandmarkDatasetEDA(train_df,DATA_DIR,resize=True)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=False)  # Không cần collate_fn","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom tqdm import tqdm\nimport cv2","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Hiển thị ảnh gốc từ train_loader# **","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torch\n\ndef show_batch(images, labels, n=8):\n    \"\"\"\n    Hiển thị n ảnh đầu tiên từ batch `images` và `labels`\n    Hỗ trợ ảnh dạng tensor (CHW) hoặc numpy (HWC)\n    \"\"\"\n    plt.figure(figsize=(15, 3))\n    for i in range(min(n, len(images))):\n        img = images[i]\n\n        # Convert Tensor → numpy\n        if isinstance(img, torch.Tensor):\n            img = img.detach().cpu().numpy()\n            if img.shape[0] == 3:  # CHW → HWC\n                img = img.transpose(1, 2, 0)\n            img = (img * 255).clip(0, 255).astype(np.uint8)\n\n        elif isinstance(img, np.ndarray) and img.ndim == 3:\n            pass  # đã là HWC\n\n        else:\n            raise ValueError(\"Ảnh phải là torch.Tensor hoặc numpy.ndarray 3 chiều\")\n\n        plt.subplot(1, n, i + 1)\n        plt.imshow(img)\n        plt.title(f\"Class {labels[i]}\")\n        plt.axis(\"off\")\n    \n    plt.tight_layout()\n    plt.show()\nfor images, labels in train_loader:\n    show_batch(images, labels, n=8)\n    break","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Phân bố class (check imbalance)**","metadata":{}},{"cell_type":"code","source":"def plot_class_distribution_from_df(df, title=\"Image/class distribution\"):\n    from collections import Counter\n    import matplotlib.pyplot as plt\n\n    class_counts = Counter(df[\"class_idx\"])\n    counts = list(class_counts.values())\n\n    plt.figure(figsize=(12, 4))\n    plt.hist(counts, bins=min(30, len(counts)))\n    plt.title(title)\n    plt.xlabel(\"Number of images\")\n    plt.ylabel(\"Number of classes\")\n    plt.grid()\n    plt.show()\n\n    print(f\"Number of classes: {len(class_counts)}\")\n    print(f\"Số ảnh min: {min(counts)}, max: {max(counts)}, trung bình: {sum(counts)//len(counts)}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"raw","source":"","metadata":{}},{"cell_type":"code","source":"plot_class_distribution_from_df(train_df)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"** Độ sáng & tương phản**","metadata":{}},{"cell_type":"code","source":"def plot_brightness_contrast(loader, max_batches=5):\n    import numpy as np\n    import matplotlib.pyplot as plt\n    import torch\n\n    brightness, contrast = [], []\n\n    with torch.no_grad():  # tắt gradient\n        for i, (images, _) in enumerate(loader):\n            if isinstance(images, torch.Tensor):\n                imgs = images.cpu().permute(0, 2, 3, 1).numpy()  # B x H x W x C\n            else:\n                imgs = np.stack(images, axis=0)  # nếu là list of np.array\n\n            gray = np.mean(imgs, axis=3)  # B x H x W\n            brightness.extend(np.mean(gray, axis=(1, 2)))\n            contrast.extend(np.std(gray, axis=(1, 2)))\n\n            if i + 1 == max_batches:\n                break\n\n    plt.figure(figsize=(12, 4))\n    plt.subplot(1, 2, 1)\n    plt.hist(brightness, bins=30)\n    plt.title(\"Brightness\")\n\n    plt.subplot(1, 2, 2)\n    plt.hist(contrast, bins=30)\n    plt.title(\"Contrast\")\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_brightness_contrast(train_loader)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**RGB trung bình (kiểm tra lệch màu)**","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport torch\n\ndef plot_rgb_distribution(loader, max_batches=5):\n    r_vals, g_vals, b_vals = [], [], []\n\n    with torch.no_grad():\n        for i, (images, _) in enumerate(loader):\n            if isinstance(images, torch.Tensor):\n                # B x C x H x W → B x H x W x C\n                imgs = images.cpu().permute(0, 2, 3, 1).numpy()\n            else:\n                imgs = np.stack(images, axis=0)\n\n            # Tính trung bình từng kênh RGB\n            r_vals.extend(imgs[:, :, :, 0].mean(axis=(1, 2)))\n            g_vals.extend(imgs[:, :, :, 1].mean(axis=(1, 2)))\n            b_vals.extend(imgs[:, :, :, 2].mean(axis=(1, 2)))\n\n            if i + 1 == max_batches:\n                break\n\n    # Vẽ biểu đồ\n    plt.hist(r_vals, alpha=0.5, label='R', bins=30)\n    plt.hist(g_vals, alpha=0.5, label='G', bins=30)\n    plt.hist(b_vals, alpha=0.5, label='B', bins=30)\n    plt.legend()\n    plt.title(\"Average RGB\")\n    plt.xlabel(\"Mean value (0–255)\")\n    plt.grid()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_rgb_distribution(train_loader)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"** Độ nét ảnh (blur score)**","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport torch\nimport cv2\n\ndef plot_blur_distribution(loader, max_batches=5):\n    blur_scores = []\n\n    with torch.no_grad():\n        for i, (images, _) in enumerate(loader):\n            if isinstance(images, torch.Tensor):\n                imgs = images.cpu().permute(0, 2, 3, 1).numpy()  # B x H x W x C\n            else:\n                imgs = np.stack(images, axis=0)\n\n            for img in imgs:\n                # Chuyển RGB → Gray (chắc chắn dùng uint8)\n                gray = cv2.cvtColor(img.astype(np.uint8), cv2.COLOR_RGB2GRAY)\n                score = cv2.Laplacian(gray, cv2.CV_64F).var()\n                blur_scores.append(score)\n\n            if i + 1 == max_batches:\n                break\n\n    # Plot\n    plt.hist(blur_scores, bins=30)\n    plt.title(\"Laplacian variance\")\n    plt.xlabel(\"Blur Score\")\n    plt.grid()\n    plt.show()\n\n    print(f\"Blur trung bình: {np.mean(blur_scores):.2f}\")\n    print(f\"Min: {np.min(blur_scores):.2f}, 📈 Max: {np.max(blur_scores):.2f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_blur_distribution(train_loader)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Data Transform and Data Loader ###","metadata":{}},{"cell_type":"code","source":"import os\nfrom PIL import Image\nfrom torch.utils.data import Dataset\n\nclass LandmarkDataset(Dataset):\n    def __init__(self, dataframe, data_dir, transform=None):\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.data_dir = data_dir\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.dataframe)\n    \n    def __getitem__(self, idx):\n        row = self.dataframe.iloc[idx]\n        img_id = row[\"id\"]\n        class_idx = int(row[\"class_idx\"])\n        \n        folder = os.path.join(self.data_dir, img_id[0], img_id[1], img_id[2])\n        img_path = os.path.join(folder, f\"{img_id}.jpg\")\n        image = Image.open(img_path).convert(\"RGB\")\n        image = np.array(image)\n        \n        if self.transform:\n            image = self.transform(image=image)[\"image\"]\n        \n        return image, class_idx","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\nfrom torch.utils.data import DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nIMAGE_SIZE = 224\nBATCH_SIZE = 32\n\ntrain_transform = A.Compose([\n    A.RandomResizedCrop(IMAGE_SIZE, IMAGE_SIZE, scale=(0.8, 1.0)),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.ImageCompression(quality_lower=99, quality_upper=100),\n    A.RandomBrightnessContrast(p=0.2),\n    A.HueSaturationValue(p=0.2),\n    A.CLAHE(p=0.1),\n    A.GaussianBlur(p=0.1),\n    A.Normalize(),\n    ToTensorV2()\n])\n\nval_transform = A.Compose([\n    A.Resize(IMAGE_SIZE, IMAGE_SIZE),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(IMAGE_SIZE)\nprint(BATCH_SIZE)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/landmark-recognition-2021/train/\"\ntrain_dataset = LandmarkDataset(train_df, DATA_DIR, transform=train_transform)\nval_dataset = LandmarkDataset(val_df, DATA_DIR, transform=val_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset_1 = np.array(train_df)\nprint(train_dataset_1.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**##WANDB_LOGIN##**","metadata":{}},{"cell_type":"code","source":"import wandb\n\nwandb.login(key=\"83b556b8b0cae1769c71ee0296dbf729527a3b1c\", relogin=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Calling out ResNet model ##","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision import models\nfrom torch.optim import Adam\nfrom tqdm import tqdm\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = models.resnet50(pretrained=True)\nmodel.fc = nn.Sequential(\n    nn.Linear(model.fc.in_features, 512),\n    nn.ReLU(),\n    nn.Dropout(0.5),\n    nn.Linear(512, 256),\n    nn.ReLU(),\n    nn.Dropout(0.5),\n    nn.Linear(256, NUM_CLASSES)\n)\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report, confusion_matrix\nimport numpy as np","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_gap(preds, confs, targets):\n    \"\"\"\n    Compute simplified GAP@20 assuming top-1 prediction per image.\n    \"\"\"\n    df = pd.DataFrame({\n        \"pred\": preds,\n        \"conf\": confs,\n        \"target\": targets\n    })\n\n    # Sort globally by confidence\n    df = df.sort_values(\"conf\", ascending=False).reset_index(drop=True)\n\n    correct = 0\n    total_precision = 0.0\n\n    for i, row in df.iterrows():\n        if row[\"pred\"] == row[\"target\"]:\n            correct += 1\n            total_precision += correct / (i + 1)\n\n    return total_precision / len(df)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Số class (NUM_CLASSES):\", NUM_CLASSES)\nprint(\"Min label:\", train_df[\"class_idx\"].min())\nprint(\"Max label:\", train_df[\"class_idx\"].max())\nprint(\"Label duy nhất:\", sorted(train_df[\"class_idx\"].unique())[:10])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import wandb\nfrom sklearn.metrics import accuracy_score\nimport numpy as np\nimport torch\nimport os\n# Khởi tạo wandb\nwandb.init(\n    project=\"landmark-recognition\",\n    name=\"resnet50-run\",\n    config={\n        \"epochs\": 20,\n        \"model\": \"resnet50\",\n        \"optimizer\": \"AdamW\",\n        \"lr\": 1e-4,\n        \"batch_size\": train_loader.batch_size,\n        \"image_size\": 224,\n    }\n)\n\nbest_gap = 0.0\nEPOCHS = wandb.config.epochs\n\nfor epoch in range(EPOCHS):\n    model.train()\n    train_loss = 0.0\n\n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item()\n\n    avg_loss = train_loss / len(train_loader)\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_loss:.4f}\")\n    \n    # Validation\n    model.eval()\n    all_preds, all_labels, all_confs = [], [], []\n\n    for images, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1} - Validation\"):\n        images, labels = images.to(device), labels.to(device)\n\n        with torch.no_grad():\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1)\n            confs, preds = torch.max(probs, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_confs.extend(confs.cpu().numpy())\n\n    acc = accuracy_score(all_labels, all_preds)\n    gap = compute_gap(all_preds, all_confs, all_labels)\n\n    print(f\"Validation Accuracy: {acc:.4f}\")\n    print(f\"GAP@20: {gap:.4f}\")\n\n    # Log metrics to wandb\n    wandb.log({\n        \"epoch\": epoch + 1,\n        \"train_loss\": avg_loss,\n        \"val_accuracy\": acc,\n        \"val_gap@20\": gap\n    })\n\n    # Save best model by GAP\n    if gap > best_gap:\n        best_gap = gap\n        torch.save(model.state_dict(), \"best_model_resnet.pth\")\n        print(f\"Saved best model (GAP={gap:.4f})\")\n\nwandb.finish()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_id = \"5551c2a604e9f9b5\"\nimg_path = f\"/kaggle/input/landmark-recognition-2021/train/{img_id[0]}/{img_id[1]}/{img_id[2]}/{img_id}.jpg\"\nos.path.exists(img_path)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample = test_df.sample(90).iloc[0]\nimg_id = sample[\"id\"]\ntrue_label = sample[\"landmark_id\"]\nclass_idx = sample[\"class_idx\"]\n\nfolder = os.path.join(DATA_DIR, img_id[0], img_id[1], img_id[2])\nimg_path = os.path.join(folder, f\"{img_id}.jpg\")\n\nimage = Image.open(img_path).convert(\"RGB\")\nimage = val_transform(image=np.array(image))[\"image\"].unsqueeze(0).to(device)\n\nmodel.eval()\nwith torch.no_grad():\n    output = model(image)\n    pred_idx = output.argmax(dim=1).item()\n\n# Reverse map\nidx_to_landmark_id = {v: k for k, v in landmark_id_to_idx.items()}\npred_landmark = idx_to_landmark_id[pred_idx]\n\nprint(f\"Image ID: {img_id}\")\nprint(f\"Ground Truth Landmark ID: {true_label}\")\nprint(f\"Predicted Landmark ID: {pred_landmark}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport PIL.Image\n\ndef show_data(img_path, title=None, size=(5, 5)):\n    \"\"\"\n    Hiển thị một ảnh từ đường dẫn img_path.\n\n    Parameters:\n        img_path (str): Đường dẫn tới ảnh\n        title (str): Tiêu đề ảnh (nếu có)\n        size (tuple): Kích thước figure matplotlib (mặc định (5, 5))\n    \"\"\"\n    try:\n        img = PIL.Image.open(img_path).convert(\"RGB\")\n        plt.figure(figsize=size)\n        plt.imshow(img)\n        plt.axis('off')\n        if title:\n            plt.title(title)\n        plt.show()\n    except FileNotFoundError:\n        print(f\"File không tồn tại: {img_path}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_path = f\"/kaggle/input/landmark-recognition-2021/train/{img_id[0]}/{img_id[1]}/{img_id[2]}/{img_id}.jpg\"\nfig = show_data(img_path)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict_single(model, test_df=val_df, data_dir=DATA_DIR, idx=5, transform=val_transform)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**#EffiecientNet#**","metadata":{}},{"cell_type":"code","source":"from torchvision import transforms\nfrom torch.utils.data import DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nIMAGE_SIZE = 260\nBATCH_SIZE = 32\n\ntrain_transform = A.Compose([\n    A.RandomResizedCrop(IMAGE_SIZE, IMAGE_SIZE, scale=(0.8, 1.0)),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.ImageCompression(quality_lower=99, quality_upper=100),\n    A.RandomBrightnessContrast(p=0.2),\n    A.HueSaturationValue(p=0.2),\n    A.CLAHE(p=0.1),\n    A.GaussianBlur(p=0.1),\n    A.Normalize(),\n    ToTensorV2()\n])\n\nval_transform = A.Compose([\n    A.Resize(IMAGE_SIZE, IMAGE_SIZE),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install efficientnet_pytorch","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom efficientnet_pytorch import EfficientNet\n\nNUM_CLASSES = 100  # hoặc len(landmark_id_to_idx)\n\n# Tải EfficientNet-B2 đã pretrained trên ImageNet\nmodel = EfficientNet.from_pretrained('efficientnet-b2')\n\n# Sửa classifier\nin_features = model._fc.in_features\nmodel._fc = nn.Linear(in_features, NUM_CLASSES)\n\n# Chuyển sang GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from efficientnet_pytorch import EfficientNet\n\nmodel = EfficientNet.from_pretrained('efficientnet-b2')\n\n# In toàn bộ cấu trúc model\nprint(model)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for images, labels in tqdm(train_loader, desc=\"Debug mode\"):\n    try:\n        images, labels = images.to(device), labels.to(device)\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n    except Exception as e:\n        print(\"Lỗi ở batch với label:\", labels)\n        raise e","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/landmark-recognition-2021/train/\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ntrain_dataset = LandmarkDataset(train_df, DATA_DIR, transform=train_transform)\nval_dataset   = LandmarkDataset(val_df,   DATA_DIR,   transform=val_transform)\n\nfrom torch.utils.data import DataLoader\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_dataset,   batch_size=32, shuffle=False, num_workers=2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import wandb\nfrom sklearn.metrics import accuracy_score\nimport numpy as np\nimport torch\n\n# Khởi tạo wandb (chỉ cần gọi 1 lần)\nwandb.init(\n    project=\"landmark-recognition\",\n    name=\"efficientnet-b0-run\",\n    config={\n        \"epochs\": 20,\n        \"model\": \"efficientnet_b0\",\n        \"optimizer\": \"AdamW\",\n        \"lr\": 1e-4,\n        \"batch_size\": train_loader.batch_size,\n        \"image_size\": 224,\n    }\n)\n\n# Khởi tạo giá trị GAP tốt nhất\nbest_gap = 0.0\n\nEPOCHS = wandb.config.epochs\n\nfor epoch in range(EPOCHS):\n    model.train()\n    train_loss = 0.0\n\n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item()\n\n    avg_loss = train_loss / len(train_loader)\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_loss:.4f}\")\n    \n    # Validation\n    model.eval()\n    all_preds, all_labels, all_confs = [], [], []\n\n    for images, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1} - Validation\"):\n        images, labels = images.to(device), labels.to(device)\n\n        with torch.no_grad():\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1)\n            confs, preds = torch.max(probs, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_confs.extend(confs.cpu().numpy())\n\n    acc = accuracy_score(all_labels, all_preds)\n    gap = compute_gap(all_preds, all_confs, all_labels)\n\n    print(f\"🔍 Validation Accuracy: {acc:.4f}\")\n    print(f\"📈 GAP@20: {gap:.4f}\")\n\n    # Log metrics to wandb\n    wandb.log({\n        \"epoch\": epoch + 1,\n        \"train_loss\": avg_loss,\n        \"val_accuracy\": acc,\n        \"val_gap@20\": gap\n    })\n\n    # Save best model by GAP\n    if gap > best_gap:\n        best_gap = gap\n        torch.save(model.state_dict(), \"best_model_efficientnet.pth\")\n        print(f\"Saved best model (GAP={gap:.4f})\")\n\nwandb.finish()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"ANOTHER TRANSFORM","metadata":{}},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom torch.utils.data import DataLoader\nIMAGE_SIZE = 256\nBATCH_SIZE =32\ntrain_transform_2 = A.Compose([\n    A.SmallestMaxSize(256),                     # Resize ngắn nhất về 256 (giữ tỉ lệ)\n    A.RandomCrop(224, 224),                     # Cắt ngẫu nhiên trung tâm vùng quan trọng\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.RandomBrightnessContrast(p=0.3),\n    A.HueSaturationValue(p=0.2),\n    A.CLAHE(p=0.1),                             # Làm rõ chi tiết (ảnh mờ)\n    A.Sharpen(alpha=(0.1, 0.3), p=0.2),         # Làm sắc nét\n    A.GaussianBlur(p=0.1),                      # Tăng generalization\n    A.Normalize(                                # Chuẩn hóa theo ImageNet\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n    ToTensorV2()\n])\nval_transform_2 = A.Compose([\n    A.Resize(IMAGE_SIZE, IMAGE_SIZE),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/landmark-recognition-2021/train/\"\ntrain_dataset_2 = LandmarkDataset(train_df, DATA_DIR, transform=train_transform_2)\nval_dataset_2 = LandmarkDataset(val_df, DATA_DIR, transform=val_transform_2)\n\ntrain_loader_2 = DataLoader(train_dataset_2, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader_2 = DataLoader(val_dataset_2, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"RESNET","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision import models\nfrom torch.optim import Adam\nfrom tqdm import tqdm\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = models.resnet50(pretrained=True)\nmodel.fc = nn.Sequential(\n    nn.Linear(model.fc.in_features, 512),\n    nn.ReLU(),\n    nn.Dropout(0.5),\n    nn.Linear(512, 256),\n    nn.ReLU(),\n    nn.Dropout(0.5),\n    nn.Linear(256, NUM_CLASSES)\n)\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport wandb\nfrom sklearn.metrics import accuracy_score\nimport numpy as np\nimport torch\n\n# Khởi tạo wandb\nwandb.init(\n    project=\"landmark-recognition\",\n    name=\"resnet50-run_ver2\",\n    config={\n        \"epochs\": 20,\n        \"model\": \"resnet50_ver2\",\n        \"optimizer\": \"AdamW\",\n        \"lr\": 1e-4,\n        \"batch_size\": train_loader_2.batch_size,\n        \"image_size\": 224,\n    }\n)\n\nbest_gap = 0.0\nEPOCHS = wandb.config.epochs\ncheckpoint_path = \"checkpoint.pth\"\n\nfor epoch in range(EPOCHS):\n    model.train()\n    train_loss = 0.0\n\n    for images, labels in tqdm(train_loader_2, desc=f\"Epoch {epoch+1} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    avg_loss = train_loss / len(train_loader_2)\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_loss:.4f}\")\n\n    # Validation\n    model.eval()\n    all_preds, all_labels, all_confs = [], [], []\n\n    for images, labels in tqdm(val_loader_2, desc=f\"Epoch {epoch+1} - Validation\"):\n        images, labels = images.to(device), labels.to(device)\n\n        with torch.no_grad():\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1)\n            confs, preds = torch.max(probs, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_confs.extend(confs.cpu().numpy())\n\n    acc = accuracy_score(all_labels, all_preds)\n    gap = compute_gap(all_preds, all_confs, all_labels)\n\n    print(f\"Validation Accuracy: {acc:.4f}\")\n    print(f\"GAP@20: {gap:.4f}\")\n\n    # Log to wandb\n    wandb.log({\n        \"epoch\": epoch + 1,\n        \"train_loss\": avg_loss,\n        \"val_accuracy\": acc,\n        \"val_gap@20\": gap\n    })\n\n    # Save best model + checkpoint\n    if gap > best_gap:\n        best_gap = gap\n        torch.save(model.state_dict(), \"best_model_resnet_ver_2.pth\")\n        print(f\"Saved best model (GAP={gap:.4f})\")\n\n        # Save checkpoint for resume\n        torch.save({\n            'epoch': epoch,\n            'model_state_dict': model.state_dict(),\n            'optimizer_state_dict': optimizer.state_dict(),\n            'best_gap': best_gap\n        }, checkpoint_path)\n        print(f\"Checkpoint saved at epoch {epoch + 1}\")\n\nwandb.finish()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\ncheckpoint = torch.load(\"/kaggle/input/model-effiecient/best_model_resnet_ver_2-2.pth\", map_location=\"cpu\")\nprint(f\"Checkpoint saved at epoch: {checkpoint['epochs']}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint = torch.load(\"/kaggle/input/model-effiecient/best_model_resnet_ver_2-2.pth\", map_location=\"cpu\")\n\n# Nếu chỉ là state_dict:\nif isinstance(checkpoint, dict) and \"model_state_dict\" not in checkpoint:\n    print(\"Đây là model state_dict, không có thông tin epoch.\")\nelse:\n    print(f\"Checkpoint saved at epoch: {checkpoint['epoch']}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport wandb\nfrom sklearn.metrics import accuracy_score\nfrom tqdm import tqdm\nimport numpy as np\n\n# Thiết lập device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Khởi tạo wandb (resume run bằng name)\nwandb.init(\n    project=\"landmark-recognition\",\n    name=\"resnet50-run_ver2\",  # giữ nguyên để nối tiếp log\n    id=\"1wepdhs5\",               # ID để wandb nhận diện đúng run\n    resume=\"allow\",               # tự động nối nếu có run trùng tên\n    config={\n        \"epochs\": 20,\n        \"model\": \"resnet50\",\n        \"optimizer\": \"AdamW\",\n        \"lr\": 1e-4,\n        \"batch_size\": train_loader_2.batch_size,\n        \"image_size\": 224,\n    }\n)\n\n# Load mô hình\ncheckpoint_path = \"/kaggle/working/best_model_resnet_ver_2.pth\"\nmodel.load_state_dict(torch.load(checkpoint_path, map_location=device))\nmodel = model.to(device)\nprint(\"Model weights loaded successfully.\")\n\n# Các biến huấn luyện\nEPOCHS = wandb.config.epochs\nstart_epoch = 13               # ← Bạn đã train xong epoch 5\nbest_gap = 0.0\n\n# Optimizer & loss\noptimizer = torch.optim.AdamW(model.parameters(), lr=wandb.config.lr)\ncriterion = torch.nn.CrossEntropyLoss()\n\n# Epoch loop\nfor epoch in range(start_epoch, EPOCHS):\n    model.train()\n    train_loss = 0.0\n\n    for images, labels in tqdm(train_loader_2, desc=f\"Epoch {epoch+1} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    avg_loss = train_loss / len(train_loader_2)\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_loss:.4f}\")\n\n    # Validation\n    model.eval()\n    all_preds, all_labels, all_confs = [], [], []\n\n    for images, labels in tqdm(val_loader_2, desc=f\"Epoch {epoch+1} - Validation\"):\n        images, labels = images.to(device), labels.to(device)\n\n        with torch.no_grad():\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1)\n            confs, preds = torch.max(probs, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_confs.extend(confs.cpu().numpy())\n\n    acc = accuracy_score(all_labels, all_preds)\n    gap = compute_gap(all_preds, all_confs, all_labels)\n\n    print(f\"Validation Accuracy: {acc:.4f}\")\n    print(f\"GAP@20: {gap:.4f}\")\n\n    # Log to wandb\n    wandb.log({\n        \"epoch\": epoch + 1,\n        \"train_loss\": avg_loss,\n        \"val_accuracy\": acc,\n        \"val_gap@20\": gap\n    })\n\n    # Save best model\n    if gap > best_gap:\n        best_gap = gap\n        torch.save(model.state_dict(), \"best_model_resnet_ver_2.pth\")\n        print(f\"Saved best model (GAP={gap:.4f})\")\n\nwandb.finish()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"EFFICIENTNET","metadata":{}},{"cell_type":"code","source":"IMAGE_SIZE = 260  # Chuẩn của EfficientNet-B2\n\ntrain_transform_2 = A.Compose([\n    A.SmallestMaxSize(288),                     # Đặt lớn hơn IMAGE_SIZE một chút để còn crop\n    A.RandomCrop(IMAGE_SIZE, IMAGE_SIZE),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.RandomBrightnessContrast(p=0.3),\n    A.HueSaturationValue(p=0.2),\n    A.CLAHE(p=0.1),\n    A.Sharpen(alpha=(0.1, 0.3), p=0.2),\n    A.GaussianBlur(p=0.1),\n    A.Normalize(\n        mean=[0.485, 0.456, 0.406],  # ImageNet stats\n        std=[0.229, 0.224, 0.225]\n    ),\n    ToTensorV2()\n])\n\nval_transform_2 = A.Compose([\n    A.Resize(IMAGE_SIZE, IMAGE_SIZE),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/landmark-recognition-2021/train/\"\ntrain_dataset_2 = LandmarkDataset(train_df, DATA_DIR, transform=train_transform_2)\nval_dataset_2 = LandmarkDataset(val_df, DATA_DIR, transform=val_transform_2)\n\ntrain_loader_2 = DataLoader(train_dataset_2, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader_2 = DataLoader(val_dataset_2, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom efficientnet_pytorch import EfficientNet\n\nNUM_CLASSES = 100  # hoặc len(landmark_id_to_idx)\n\n# Tải EfficientNet-B2 đã pretrained trên ImageNet\nmodel = EfficientNet.from_pretrained('efficientnet-b2')\n\n# Sửa classifier\nin_features = model._fc.in_features\nmodel._fc = nn.Linear(in_features, NUM_CLASSES)\n\n# Chuyển sang GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport wandb\nfrom sklearn.metrics import accuracy_score\nimport numpy as np\nimport torch\n\n# Khởi tạo wandb (chỉ cần gọi 1 lần)\nwandb.init(\n    project=\"landmark-recognition\",\n    name=\"efficientnet-b2-run\",\n    config={\n        \"epochs\": 20,\n        \"model\": \"efficientnet_b2_ver2\",\n        \"optimizer\": \"AdamW\",\n        \"lr\": 1e-4,\n        \"batch_size\": train_loader_2.batch_size,\n        \"image_size\": 260,\n    }\n)\n\n# Biến cấu hình và trạng thái\nEPOCHS = wandb.config.epochs\nbest_gap = 0.0\ncheckpoint_path = \"checkpoint_b2.pth\"\n\nfor epoch in range(EPOCHS):\n    model.train()\n    train_loss = 0.0\n\n    for images, labels in tqdm(train_loader_2, desc=f\"Epoch {epoch+1} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    avg_loss = train_loss / len(train_loader_2)\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_loss:.4f}\")\n\n    # Validation\n    model.eval()\n    all_preds, all_labels, all_confs = [], [], []\n\n    for images, labels in tqdm(val_loader_2, desc=f\"Epoch {epoch+1} - Validation\"):\n        images, labels = images.to(device), labels.to(device)\n\n        with torch.no_grad():\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1)\n            confs, preds = torch.max(probs, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_confs.extend(confs.cpu().numpy())\n\n    acc = accuracy_score(all_labels, all_preds)\n    gap = compute_gap(all_preds, all_confs, all_labels)\n\n    print(f\"Validation Accuracy: {acc:.4f}\")\n    print(f\"GAP@20: {gap:.4f}\")\n\n    # Log metrics to wandb\n    wandb.log({\n        \"epoch\": epoch + 1,\n        \"train_loss\": avg_loss,\n        \"val_accuracy\": acc,\n        \"val_gap@20\": gap\n    })\n\n    # Save best model by GAP + checkpoint\n    if gap > best_gap:\n        best_gap = gap\n        torch.save(model.state_dict(), \"best_model_efficientnet_ver_2.pth\")\n        print(f\"Saved best model (GAP={gap:.4f})\")\n\n        # Save full checkpoint\n        torch.save({\n            'epoch': epoch,\n            'model_state_dict': model.state_dict(),\n            'optimizer_state_dict': optimizer.state_dict(),\n            'best_gap': best_gap\n        }, checkpoint_path)\n        print(f\"Checkpoint saved at epoch {epoch + 1}\")\n\nwandb.finish()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport wandb\nfrom sklearn.metrics import accuracy_score\nfrom tqdm import tqdm\nimport numpy as np\n\n# Thiết lập device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n\nwandb.init(\n    project=\"landmark-recognition\",\n    name=\"efficientnet-b2-run\",  # giữ nguyên để nối tiếp log\n    id=\"fm1g0owp\",               # ID để wandb nhận diện đúng run\n    resume=\"allow\",               # tự động nối nếu có run trùng tên\n    config={\n        \"epochs\": 20,\n        \"model\": \"efficientnet_b2_ver2\",\n        \"optimizer\": \"AdamW\",\n        \"lr\": 1e-4,\n        \"batch_size\": train_loader_2.batch_size,\n        \"image_size\": 260,\n    }\n)\n\n# Load mô hình\ncheckpoint_path = \"/kaggle/input/model-effiecient/best_model_efficientnet_ver_2-2.pth\"\nmodel.load_state_dict(torch.load(checkpoint_path, map_location=device))\nmodel = model.to(device)\nprint(\"Model weights loaded successfully.\")\n\n# Các biến huấn luyện\nEPOCHS = wandb.config.epochs\nstart_epoch = 12               # ← Bạn đã train xong epoch 5\nbest_gap = 0.9527\n\n# Optimizer & loss\noptimizer = torch.optim.AdamW(model.parameters(), lr=wandb.config.lr)\ncriterion = torch.nn.CrossEntropyLoss()\n\n# Epoch loop\nfor epoch in range(start_epoch, EPOCHS):\n    model.train()\n    train_loss = 0.0\n\n    for images, labels in tqdm(train_loader_2, desc=f\"Epoch {epoch+1} - Training\"):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    avg_loss = train_loss / len(train_loader_2)\n    print(f\"Epoch {epoch+1} | Train Loss: {avg_loss:.4f}\")\n\n    # Validation\n    model.eval()\n    all_preds, all_labels, all_confs = [], [], []\n\n    for images, labels in tqdm(val_loader_2, desc=f\"Epoch {epoch+1} - Validation\"):\n        images, labels = images.to(device), labels.to(device)\n\n        with torch.no_grad():\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1)\n            confs, preds = torch.max(probs, dim=1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n        all_confs.extend(confs.cpu().numpy())\n\n    acc = accuracy_score(all_labels, all_preds)\n    gap = compute_gap(all_preds, all_confs, all_labels)\n\n    print(f\"Validation Accuracy: {acc:.4f}\")\n    print(f\"GAP@20: {gap:.4f}\")\n\n    # Log to wandb\n    wandb.log({\n        \"epoch\": epoch + 1,\n        \"train_loss\": avg_loss,\n        \"val_accuracy\": acc,\n        \"val_gap@20\": gap\n    })\n\n    # Save best model\n    if gap > best_gap:\n        best_gap = gap\n        torch.save(model.state_dict(), \"best_model_resnet_ver_2.pth\")\n        print(f\"Saved best model (GAP={gap:.4f})\")\n\nwandb.finish()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}