{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:05.458915Z","iopub.execute_input":"2025-07-25T08:57:05.459203Z","iopub.status.idle":"2025-07-25T08:57:06.324136Z","shell.execute_reply.started":"2025-07-25T08:57:05.459177Z","shell.execute_reply":"2025-07-25T08:57:06.323376Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dogs vs. Cats: Vision Transformer (ViT) による転移学習\nこのノートブックでは、Vision Transformer (ViT) の事前学習済みモデルを用いて、「Dogs vs. Cats」コンペティションの画像分類タスクに取り組みます。","metadata":{}},{"cell_type":"markdown","source":"# 1. 準備 (Setup)\n必要なライブラリをすべてインポートし、基本的な設定を行います。","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport zipfile\nfrom glob import glob\nfrom tqdm.auto import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport torchvision.models as models\nfrom torchvision import transforms\nfrom torchvision.models import vit_b_16, ViT_B_16_Weights\n\nfrom sklearn.model_selection import train_test_split\nfrom PIL import Image\n\n%config Completer.use_jedi = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:06.325361Z","iopub.execute_input":"2025-07-25T08:57:06.325660Z","iopub.status.idle":"2025-07-25T08:57:18.113407Z","shell.execute_reply.started":"2025-07-25T08:57:06.325642Z","shell.execute_reply":"2025-07-25T08:57:18.112660Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. データの解凍 (Data Extraction)\nKaggleから提供される.zip形式のデータセットを、書き込み可能な /kaggle/working/ ディレクトリに解凍します。","metadata":{}},{"cell_type":"code","source":"INPUT_DIR = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/'\nOUTPUT_DIR = '/kaggle/working/'\n\ntrain_zip_path = os.path.join(INPUT_DIR, 'train.zip')\ntest_zip_path = os.path.join(INPUT_DIR, 'test.zip')\n\nprint(\"訓練データ(train.zip)を解凍しています...\")\nwith zipfile.ZipFile(train_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(OUTPUT_DIR)\nprint(\"解凍が完了しました。\")\n\nprint(\"テストデータ(test.zip)を解凍しています...\")\nwith zipfile.ZipFile(test_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(OUTPUT_DIR)\nprint(\"解凍が完了しました。\")\n\nTRAIN_IMAGE_DIR = os.path.join(OUTPUT_DIR, 'train')\nTEST_IMAGE_DIR = os.path.join(OUTPUT_DIR, 'test')\n\nprint(f\"\\nTrain images path: {TRAIN_IMAGE_DIR}\")\nprint(f\"Test images path: {TEST_IMAGE_DIR}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:18.114198Z","iopub.execute_input":"2025-07-25T08:57:18.114593Z","iopub.status.idle":"2025-07-25T08:57:33.141923Z","shell.execute_reply.started":"2025-07-25T08:57:18.114569Z","shell.execute_reply":"2025-07-25T08:57:33.141145Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. データセットとデータローダーの定義 (Dataset and DataLoader)¶\nPyTorchの Dataset と DataLoader を使って、モデルに入力するためのデータパイプラインを構築します。ViTの学習済みモデルに最適化された画像変換処理もここで定義します。","metadata":{}},{"cell_type":"code","source":"weights = ViT_B_16_Weights.DEFAULT\npreprocess = weights.transforms()\n\nprint(\"ViTモデル用の前処理パイプライン:\")\nprint(preprocess)\n\nclass DogsVsCatsDataset(Dataset):\n    def __init__(self, file_paths, transform=None, is_test=False):\n        self.file_paths = file_paths\n        self.transform = transform\n        self.is_test = is_test\n        self.label_map = {\"cat\": 0, \"dog\": 1}\n\n    def __len__(self):\n        return len(self.file_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.file_paths[idx]\n        image = Image.open(img_path).convert(\"RGB\")\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        if self.is_test:\n            return image, os.path.basename(img_path)\n        else:\n            label_name = os.path.basename(img_path).split('.')[0]\n            label = self.label_map[label_name]\n            return image, label\n\nall_files = glob(os.path.join(TRAIN_IMAGE_DIR, '*.jpg'))\ntrain_files, val_files = train_test_split(all_files, test_size=0.2, random_state=42, stratify=[fn.split('.')[0] for fn in os.listdir(TRAIN_IMAGE_DIR)])\n\ntrain_dataset = DogsVsCatsDataset(file_paths=train_files, transform=preprocess)\nval_dataset = DogsVsCatsDataset(file_paths=val_files, transform=preprocess)\n\nBATCH_SIZE = 32\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)\n\nprint(f\"訓練データの数: {len(train_dataset)}\")\nprint(f\"検証データの数: {len(val_dataset)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:33.143575Z","iopub.execute_input":"2025-07-25T08:57:33.143808Z","iopub.status.idle":"2025-07-25T08:57:33.234243Z","shell.execute_reply.started":"2025-07-25T08:57:33.143789Z","shell.execute_reply":"2025-07-25T08:57:33.233627Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. モデルの定義と準備 (Model Definition)¶\nImageNetで事前学習済みのViTモデルをロードし、最終的な分類層を今回のタスク（2クラス分類）に合わせて変更します。","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\nmodel = vit_b_16(weights=weights)\n\nnum_features = model.heads.head.in_features\nmodel.heads.head = nn.Linear(num_features, 2)\n\nmodel = model.to(device)\n\nprint(\"モデルの最終層（ヘッド）が変更されました:\")\nprint(model.heads)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:33.235021Z","iopub.execute_input":"2025-07-25T08:57:33.235301Z","iopub.status.idle":"2025-07-25T08:57:36.983889Z","shell.execute_reply.started":"2025-07-25T08:57:33.235277Z","shell.execute_reply":"2025-07-25T08:57:36.983148Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. 学習・検証ループの定義 (Training & Validation Loop)\n1エポック分の学習と検証を行うための関数を定義します。","metadata":{}},{"cell_type":"code","source":"def train_one_epoch(model, train_loader, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    correct_predictions = 0\n    total_samples = 0\n    \n    for images, labels in tqdm(train_loader, desc=\"Training\"):\n        images = images.to(device)\n        labels = 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        running_loss += loss.item() * images.size(0)\n        _, preds = torch.max(outputs, 1)\n        correct_predictions += torch.sum(preds == labels.data)\n        total_samples += labels.size(0)\n        \n    epoch_loss = running_loss / total_samples\n    epoch_acc = correct_predictions.double() / total_samples\n    return epoch_loss, epoch_acc.item()\n\ndef validate_one_epoch(model, val_loader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    correct_predictions = 0\n    total_samples = 0\n    \n    with torch.no_grad():\n        for images, labels in tqdm(val_loader, desc=\"Validation\"):\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n            running_loss += loss.item() * images.size(0)\n            _, preds = torch.max(outputs, 1)\n            correct_predictions += torch.sum(preds == labels.data)\n            total_samples += labels.size(0)\n            \n    epoch_loss = running_loss / total_samples\n    epoch_acc = correct_predictions.double() / total_samples\n    return epoch_loss, epoch_acc.item()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:36.984625Z","iopub.execute_input":"2025-07-25T08:57:36.984895Z","iopub.status.idle":"2025-07-25T08:57:36.992981Z","shell.execute_reply.started":"2025-07-25T08:57:36.984874Z","shell.execute_reply":"2025-07-25T08:57:36.992268Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. 転移学習の実行 (Execute Transfer Learning)\n損失関数とオプティマイザを設定し、定義したループ関数を使って学習を実行します。検証精度が最も良かったモデルを保存します。","metadata":{}},{"cell_type":"code","source":"NUM_EPOCHS = 3\nLEARNING_RATE = 1e-5\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)\nscheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)\n\nbest_val_acc = 0.0\nMODEL_SAVE_PATH = '/kaggle/working/best_model.pth'\n\nfor epoch in range(NUM_EPOCHS):\n    print(f\"--- Epoch {epoch+1}/{NUM_EPOCHS} ---\")\n    \n    train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)\n    print(f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}\")\n    \n    val_loss, val_acc = validate_one_epoch(model, val_loader, criterion, device)\n    print(f\"Validation Loss: {val_loss:.4f}, Validation Acc: {val_acc:.4f}\")\n    \n    scheduler.step()\n\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), MODEL_SAVE_PATH)\n        print(f\"Best model saved with validation accuracy: {best_val_acc:.4f}\")\n\nprint(\"\\n--- 学習完了 ---\")\nprint(f\"最終的なベスト検証精度: {best_val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T08:57:36.993828Z","iopub.execute_input":"2025-07-25T08:57:36.994230Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7. 提出ファイルの作成 (Create Submission File)\n学習したモデルの中で最も性能の良かったモデルをロードし、テストデータに対する予測を行います。その結果をKaggleの提出フォーマットに合わせてCSVファイルとして出力します。","metadata":{}},{"cell_type":"code","source":"print(\"提出用ファイルの作成を開始します...\")\n\nmodel.load_state_dict(torch.load(MODEL_SAVE_PATH))\nmodel.eval()\n\ntest_files = sorted(glob(os.path.join(TEST_IMAGE_DIR, '*.jpg')), key=lambda x: int(os.path.basename(x).split('.')[0]))\ntest_dataset = DogsVsCatsDataset(file_paths=test_files, transform=preprocess, is_test=True)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\nall_preds = []\nall_ids = []\n\nwith torch.no_grad():\n    for images, filenames in tqdm(test_loader, desc=\"Predicting\"):\n        images = images.to(device)\n        outputs = model(images)\n        \n        # 確率に変換 (dogクラスである確率)\n        probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()\n        all_preds.extend(probs)\n        \n        ids = [int(fn.split('.')[0]) for fn in filenames]\n        all_ids.extend(ids)\n        \nsubmission_df = pd.DataFrame({'id': all_ids, 'label': all_preds})\n# idでソートして提出フォーマットを保証\nsubmission_df = submission_df.sort_values(by='id').reset_index(drop=True)\n\n# ラベルの値をクリッピング (0に近い/1に近い極端な値を避ける)\nsubmission_df['label'] = np.clip(submission_df['label'], 0.005, 0.995)\n\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"\\nsubmission.csv が正常に作成されました。\")\nprint(submission_df.head())","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}