{"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":7327,"databundleVersionId":861871},{"sourceType":"modelInstanceVersion","sourceId":862664,"databundleVersionId":17129689,"modelInstanceId":655816,"modelId":667794}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom torch.utils.data import DataLoader\n\nDATA_DIR = '/kaggle/input/competitions/dog-breed-identification'\nTRAIN_CSV = os.path.join(DATA_DIR, 'labels.csv')\nTRAIN_IMG_DIR = os.path.join(DATA_DIR, 'train')\nTEST_IMG_DIR = os.path.join(DATA_DIR, 'test')\n\ndf = pd.read_csv(TRAIN_CSV)\ntrain_df, val_df = train_test_split(df, test_size=0.2, stratify=df['breed'], random_state=42)\ntrain_df = train_df.reset_index(drop=True)\nval_df = val_df.reset_index(drop=True)\n\nbreeds = sorted(df['breed'].unique())\nbreed_to_idx = {breed: idx for idx, breed in enumerate(breeds)}\n\nprint(f\"训练集大小: {len(train_df)}, 验证集大小: {len(val_df)}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-11T09:26:49.184924Z","iopub.execute_input":"2026-05-11T09:26:49.185212Z","iopub.status.idle":"2026-05-11T09:26:49.219701Z","shell.execute_reply.started":"2026-05-11T09:26:49.185186Z","shell.execute_reply":"2026-05-11T09:26:49.219120Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nfrom torch.utils.data import Dataset\nfrom torchvision import transforms\n\n# 定义 Dataset 类\nclass DogBreedDataset(Dataset):\n    def __init__(self, csv_df, img_dir, transform=None, breed_to_idx=None):\n        self.df = csv_df\n        self.img_dir = img_dir\n        self.transform = transform\n        self.breed_to_idx = breed_to_idx\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_name = os.path.join(self.img_dir, self.df.iloc[idx, 0] + '.jpg')\n        image = Image.open(img_name).convert('RGB')\n        label = self.breed_to_idx[self.df.iloc[idx, 1]]\n        if self.transform:\n            image = self.transform(image)\n        return image, label\n\n# 定义数据增强与处理\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(384, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((384, 384)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# 实例化 DataLoader\ntrain_dataset = DogBreedDataset(csv_df=train_df, img_dir=TRAIN_IMG_DIR, transform=train_transform, breed_to_idx=breed_to_idx)\nval_dataset = DogBreedDataset(csv_df=val_df, img_dir=TRAIN_IMG_DIR, transform=val_transform, breed_to_idx=breed_to_idx)\n\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=2, pin_memory=True)\nval_loader = DataLoader(val_dataset, batch_size=128, shuffle=False, num_workers=2, pin_memory=True)\n\nprint(\"DataLoader 准备完毕！\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T09:26:49.221297Z","iopub.execute_input":"2026-05-11T09:26:49.221855Z","iopub.status.idle":"2026-05-11T09:26:49.231750Z","shell.execute_reply.started":"2026-05-11T09:26:49.221830Z","shell.execute_reply":"2026-05-11T09:26:49.230883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import timm\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\nfrom tqdm.notebook import tqdm\nimport os\n\n# 模型与初始化\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = timm.create_model('vit_large_patch16_384', pretrained=True, num_classes=120).to(device)\n\n# 线性探测\nfor name, param in model.named_parameters():\n    if \"head\" not in name:\n        param.requires_grad = False\nprint(\"已经冻结主干网络，仅训练分类头！\")\nif torch.cuda.device_count() > 1:\n    print(f\"Let's use {torch.cuda.device_count()} GPUs!\")\n    model = nn.DataParallel(model)\n\n# 优化器、损失函数与调度器\nEPOCHS = 10\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n# 针对 ViT，学习率可以稍微大一点点配合 CosineAnnealing\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)\nscheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS) \nscaler = torch.cuda.amp.GradScaler() # AMP 缩放器\n\n# 训练与验证循环\nbest_val_acc = 0.0\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss = 0.0\n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} Train\")\n    for images, labels in pbar:\n        images, labels = images.to(device), labels.to(device)\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        running_loss += loss.item()\n        current_lr = optimizer.param_groups[0]['lr']\n        pbar.set_postfix({'loss': f\"{running_loss / (pbar.n + 1):.4f}\", 'lr': f\"{current_lr:.6f}\"})\n        \n    scheduler.step()\n        \n    # 验证阶段\n    model.eval()\n    correct = 0\n    total = 0\n    with torch.no_grad():\n        for images, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} Val\"):\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            _, predicted = torch.max(outputs.data, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            \n    val_acc = 100 * correct / total\n    print(f'Epoch {epoch+1} Validation Accuracy: {val_acc:.2f}%')\n    \n    # 模型保存\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        # 如果是多卡模型，提取 module 的权重；如果是单卡，直接提取\n        model_to_save = model.module if isinstance(model, nn.DataParallel) else model\n        torch.save(model_to_save.state_dict(), '/kaggle/working/best_vit_model.pth')\n        print(f\"New Best Model saved with Acc: {val_acc:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T09:26:49.232783Z","iopub.execute_input":"2026-05-11T09:26:49.233038Z","iopub.status.idle":"2026-05-11T09:31:57.660027Z","shell.execute_reply.started":"2026-05-11T09:26:49.233016Z","shell.execute_reply":"2026-05-11T09:31:57.659001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nimport torch.nn.functional as F\nfrom torchvision import transforms\nfrom PIL import Image\nfrom tqdm.notebook import tqdm\nimport timm\n\ntta_transforms = [\n    transforms.Compose([\n        transforms.Resize((384, 384)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ]),\n    transforms.Compose([\n        transforms.Resize((384, 384)),\n        transforms.RandomHorizontalFlip(p=1.0), # p=1.0 保证 100% 翻转\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ]),\n    transforms.Compose([\n        transforms.Resize((400, 400)),\n        transforms.CenterCrop(384),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n]\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ninference_model = timm.create_model('vit_large_patch16_384', pretrained=False, num_classes=120).to(device)\n\ninference_model.load_state_dict(torch.load('/kaggle/working/best_vit_model.pth'))\ninference_model.eval()\n\ntest_images = [f for f in os.listdir(TEST_IMG_DIR) if f.endswith('.jpg')]\npredictions = []\nids = []\n\nwith torch.no_grad():\n    for img_name in tqdm(test_images, desc=\"TTA Predicting Test Set\"):\n        img_path = os.path.join(TEST_IMG_DIR, img_name)\n        raw_image = Image.open(img_path).convert('RGB')\n        \n        tta_probs = []\n        \n        for t in tta_transforms:\n            img_tensor = t(raw_image).unsqueeze(0).to(device)\n            outputs = inference_model(img_tensor)\n            probs = F.softmax(outputs, dim=1).cpu().numpy()[0]\n            tta_probs.append(probs)\n            \n        avg_probs = sum(tta_probs) / len(tta_transforms)\n        \n        predictions.append(avg_probs)\n        ids.append(img_name.split('.')[0])\n\nsubmission_df = pd.DataFrame(predictions, columns=breeds)\nsubmission_df.insert(0, 'id', ids)\nsubmission_df.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(\"TTA success!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-11T09:31:57.660828Z","iopub.status.idle":"2026-05-11T09:31:57.661164Z","shell.execute_reply.started":"2026-05-11T09:31:57.661034Z","shell.execute_reply":"2026-05-11T09:31:57.661050Z"}},"outputs":[],"execution_count":null}]}