{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31041,"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-18T07:37:02.623297Z","iopub.execute_input":"2025-07-18T07:37:02.623536Z","iopub.status.idle":"2025-07-18T07:37:03.157486Z","shell.execute_reply.started":"2025-07-18T07:37:02.623508Z","shell.execute_reply":"2025-07-18T07:37:03.156745Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os, glob, time, copy, zipfile\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom tqdm import tqdm\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.utils.data as data\nimport torch.nn.functional as F\nfrom torchvision import models, transforms\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:03.159256Z","iopub.execute_input":"2025-07-18T07:37:03.159554Z","iopub.status.idle":"2025-07-18T07:37:11.342823Z","shell.execute_reply.started":"2025-07-18T07:37:03.159531Z","shell.execute_reply":"2025-07-18T07:37:11.342032Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ハイパーパラメータの設定","metadata":{}},{"cell_type":"code","source":"size = 224\nmean = (0.485, 0.456, 0.406)\nstd = (0.229, 0.224, 0.225)\nbatch_size = 32\nnum_epoch = 25\nlearning_rate = 0.0001\npatience = 5\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:11.345944Z","iopub.execute_input":"2025-07-18T07:37:11.346114Z","iopub.status.idle":"2025-07-18T07:37:11.408564Z","shell.execute_reply.started":"2025-07-18T07:37:11.346100Z","shell.execute_reply":"2025-07-18T07:37:11.407737Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 画像の前処理クラス\n","metadata":{}},{"cell_type":"code","source":"class ImageTransform():\n    def __init__(self, resize, mean, std):\n        self.data_transform = {\n            'train': transforms.Compose([\n                transforms.Resize((resize, resize)),\n                transforms.RandomHorizontalFlip(),\n                transforms.RandomRotation(10),\n                transforms.ColorJitter(brightness=0.2, contrast=0.2),\n                transforms.ToTensor(),\n                transforms.Normalize(mean, std)\n            ]),\n            'val': transforms.Compose([\n                transforms.Resize((resize, resize)),\n                transforms.ToTensor(),\n                transforms.Normalize(mean, std)\n            ])\n        }\n    \n    def __call__(self, img, phase):\n        return self.data_transform[phase](img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:11.409413Z","iopub.execute_input":"2025-07-18T07:37:11.409624Z","iopub.status.idle":"2025-07-18T07:37:11.429739Z","shell.execute_reply.started":"2025-07-18T07:37:11.409606Z","shell.execute_reply":"2025-07-18T07:37:11.429150Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データセットクラス","metadata":{}},{"cell_type":"code","source":"class DogCatDataset(data.Dataset):\n    def __init__(self, file_list, labels, transform=None, phase='train'):\n        self.file_list = file_list\n        self.labels = labels\n        self.transform = transform\n        self.phase = phase\n\n    def __len__(self):\n        return len(self.file_list)\n\n    def __getitem__(self, index):\n        img_path = self.file_list[index]\n        img = Image.open(img_path).convert(\"RGB\")\n        img = self.transform(img, self.phase)\n        label = self.labels[index]\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:11.430459Z","iopub.execute_input":"2025-07-18T07:37:11.430920Z","iopub.status.idle":"2025-07-18T07:37:11.447584Z","shell.execute_reply.started":"2025-07-18T07:37:11.430901Z","shell.execute_reply":"2025-07-18T07:37:11.446966Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データをリセット\nコマンドで初期化させる\n","metadata":{}},{"cell_type":"code","source":"!rm -rf ./data\n!rm -f ./best_model.pth","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:11.449875Z","iopub.execute_input":"2025-07-18T07:37:11.450098Z","iopub.status.idle":"2025-07-18T07:37:11.717645Z","shell.execute_reply.started":"2025-07-18T07:37:11.450082Z","shell.execute_reply":"2025-07-18T07:37:11.716831Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データの解凍とラベル付け\n読み込み→解凍→ラベルつけ→学習・検証データを8:2で分割","metadata":{}},{"cell_type":"code","source":"# データ読み込みとラベル付け\nbase_dir = '../input/dogs-vs-cats-redux-kernels-edition'\ntrain_dir = './data/train'\ntest_dir = './data/test'\n\nos.makedirs('./data', exist_ok=True)\n\nwith zipfile.ZipFile(os.path.join(base_dir, 'train.zip')) as train_zip:\n    train_zip.extractall('./data')\nwith zipfile.ZipFile(os.path.join(base_dir, 'test.zip')) as test_zip:\n    test_zip.extractall('./data')\n\n# すべての画像パスを取得\nall_list = glob.glob(os.path.join(train_dir, '*.jpg'))\ntest_list = glob.glob(os.path.join(test_dir, '*.jpg'))\n\n# ラベル（画像ファイル名に cat or dog を含む）\ndef get_label(file_list):\n    labels = []\n    for path in file_list:\n        filename = os.path.basename(path)\n        if 'dog' in filename:\n            labels.append(1)\n        else:\n            labels.append(0)\n    return labels\n\nall_labels = get_label(all_list)\n\n\ntrain_list, val_list, train_labels, val_labels = train_test_split(\n    all_list, all_labels,\n    test_size=0.2,\n    random_state=42,\n    stratify=all_labels\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:11.718545Z","iopub.execute_input":"2025-07-18T07:37:11.718777Z","iopub.status.idle":"2025-07-18T07:37:25.654627Z","shell.execute_reply.started":"2025-07-18T07:37:11.718740Z","shell.execute_reply":"2025-07-18T07:37:25.653949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Train: {len(train_list)} 枚\")\nprint(f\"Val:   {len(val_list)} 枚\")\nprint(f\"Test:  {len(test_list)} 枚\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:25.655478Z","iopub.execute_input":"2025-07-18T07:37:25.655693Z","iopub.status.idle":"2025-07-18T07:37:25.660391Z","shell.execute_reply.started":"2025-07-18T07:37:25.655675Z","shell.execute_reply":"2025-07-18T07:37:25.659602Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データセットとデータローダの作成","metadata":{}},{"cell_type":"code","source":"transform = ImageTransform(size, mean, std)\n\ntrain_dataset = DogCatDataset(\n    train_list,\n    train_labels,\n    transform,\n    phase='train'\n)\n\nval_dataset = DogCatDataset(\n    val_list,\n    val_labels,\n    transform,\n    phase='val'\n)\n\ntrain_loader = data.DataLoader(\n    train_dataset,\n    batch_size=batch_size,\n    shuffle=True,\n    num_workers=6,\n    pin_memory=True\n)\n\nval_loader = data.DataLoader(\n    val_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=6,\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:25.661208Z","iopub.execute_input":"2025-07-18T07:37:25.661885Z","iopub.status.idle":"2025-07-18T07:37:25.679981Z","shell.execute_reply.started":"2025-07-18T07:37:25.661866Z","shell.execute_reply":"2025-07-18T07:37:25.679265Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## モデルや変数の定義\n中盤までの層の重みを更新しない","metadata":{}},{"cell_type":"code","source":"# ResNet18の読み込み（ImageNetで事前学習済み）\nmodel = models.resnet34(pretrained=True)\n\n# 全てのパラメータを一旦固定（requires_grad=False）\nfor param in model.parameters():\n    param.requires_grad = False\n\n# ✅ layer3, layer4 のみ微調整\nfor param in model.layer3.parameters():\n    param.requires_grad = True\nfor param in model.layer4.parameters():\n    param.requires_grad = True\n\n# fc層（全結合）を2クラス分類用に置き換え\nmodel.fc = nn.Linear(model.fc.in_features, 2)\n\n# モデルをデバイスに送る\nmodel = model.to(device)\n\n# 損失関数\ncriterion = nn.CrossEntropyLoss()\n\n# ✅ 最適化対象：layer3, layer4, fc\nparams_to_update = (\n    list(model.layer3.parameters()) +\n    list(model.layer4.parameters()) +\n    list(model.fc.parameters())\n)\noptimizer = optim.Adam(params_to_update, lr=0.0002)\n\n# 学習率スケジューラー（5エポックごとに半分に）\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:25.680720Z","iopub.execute_input":"2025-07-18T07:37:25.680993Z","iopub.status.idle":"2025-07-18T07:37:26.894210Z","shell.execute_reply.started":"2025-07-18T07:37:25.680969Z","shell.execute_reply":"2025-07-18T07:37:26.893432Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 学習\n","metadata":{}},{"cell_type":"code","source":"best_acc = 0.0\nearly_stop_counter = 0\n\n# 損失と精度を記録するリスト\ntrain_losses = []\nval_losses = []\ntrain_accuracies = []\nval_accuracies = []\n\nfor epoch in range(num_epoch):\n    model.train()\n    train_loss = 0.0\n    train_correct = 0\n    train_total = 0\n\n    for inputs, labels in tqdm(train_loader):\n        inputs = inputs.to(device)\n        labels = labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n        _, predicted = torch.max(outputs, 1)\n        train_correct += (predicted == labels).sum().item()\n        train_total += labels.size(0)\n\n    train_acc = train_correct / train_total\n    avg_train_loss = train_loss / len(train_loader)\n\n    # === 検証フェーズ ===\n    model.eval()\n    val_loss = 0.0\n    val_correct = 0\n    val_total = 0\n\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n\n            val_loss += loss.item()\n            _, predicted = torch.max(outputs, 1)\n            val_correct += (predicted == labels).sum().item()\n            val_total += labels.size(0)\n\n    val_acc = val_correct / val_total\n    avg_val_loss = val_loss / len(val_loader)\n\n    print(f'Epoch [{epoch+1}/{num_epoch}] '\n          f'Train Loss: {avg_train_loss:.4f}, Train Acc: {train_acc:.4f} | '\n          f'Val Loss: {avg_val_loss:.4f}, Val Acc: {val_acc:.4f}')\n    \n    # 損失と精度を記録\n    train_losses.append(avg_train_loss)\n    val_losses.append(avg_val_loss)\n    train_accuracies.append(train_acc)\n    val_accuracies.append(val_acc)\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        early_stop_counter = 0  # 改善したのでリセット\n        torch.save(model.state_dict(), './best_model.pth')\n        print('✅ Best model updated and saved!')\n    else:\n        early_stop_counter += 1\n        print(f'⏳ No improvement... [{early_stop_counter}/{patience}]')\n        if early_stop_counter >= patience:\n            print('🛑 Early stopping triggered.')\n            break\n    #学習率を調整\n    scheduler.step()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T07:37:26.895105Z","iopub.execute_input":"2025-07-18T07:37:26.895661Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ロスと精度の可視化","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# === 損失の推移グラフ ===\nplt.figure(figsize=(10, 4))\nplt.subplot(1, 2, 1)\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss Over Epochs')\nplt.legend()\nplt.grid()\n\n# === 精度の推移グラフ ===\nplt.subplot(1, 2, 2)\nplt.plot(train_accuracies, label='Train Acc')\nplt.plot(val_accuracies, label='Val Acc')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Accuracy Over Epochs')\nplt.legend()\nplt.grid()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 推論\n","metadata":{}},{"cell_type":"code","source":"# モデルの重みを読み込む（念のため）\nmodel.load_state_dict(torch.load('./best_model.pth'))\nmodel.eval()\n\n# 予測結果を保存するリスト\nresults = []\n\n# 画像の前処理（バリデーションと同じ）\ntransform = ImageTransform(size, mean, std)\n\nfor path in tqdm(test_list):\n    img = Image.open(path).convert(\"RGB\")\n    img = transform(img, phase='val')\n    img = img.unsqueeze(0)  # バッチ次元を追加 (1, 3, H, W)\n    img = img.to(device)\n\n    with torch.no_grad():\n        outputs = model(img)  # shape: [1, 2]\n        probs = torch.softmax(outputs, dim=1)  # shape: [1, 2]\n        dog_prob = probs[0][1].item()  # 犬（クラス1）の確率だけ取得\n\n    # ファイル名（例: 1234.jpg）と予測確率（0〜1）\n    id = os.path.basename(path).split('.')[0]\n    results.append([int(id), dog_prob])\n\n# DataFrameとしてCSVに保存\ndf = pd.DataFrame(results, columns=['id', 'label'])\ndf.sort_values('id', inplace=True)\ndf.to_csv('submission.csv', index=False)\n\nprint(\"✅ 推論完了（確率で出力）submission.csv を作成しました。\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}