{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.11"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":790.524959,"end_time":"2025-07-18T07:02:14.121939","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-07-18T06:49:03.596980","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"84170154","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":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2025-07-18T06:49:08.082848Z","iopub.status.busy":"2025-07-18T06:49:08.082605Z","iopub.status.idle":"2025-07-18T06:49:09.651089Z","shell.execute_reply":"2025-07-18T06:49:09.650253Z"},"papermill":{"duration":1.574707,"end_time":"2025-07-18T06:49:09.652359","exception":false,"start_time":"2025-07-18T06:49:08.077652","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"ed580ffb","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:09.660470Z","iopub.status.busy":"2025-07-18T06:49:09.659950Z","iopub.status.idle":"2025-07-18T06:49:21.092983Z","shell.execute_reply":"2025-07-18T06:49:21.092380Z"},"papermill":{"duration":11.438278,"end_time":"2025-07-18T06:49:21.094358","exception":false,"start_time":"2025-07-18T06:49:09.656080","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"68afdcf5","cell_type":"markdown","source":"## ハイパーパラメータの設定","metadata":{"papermill":{"duration":0.003092,"end_time":"2025-07-18T06:49:21.101188","exception":false,"start_time":"2025-07-18T06:49:21.098096","status":"completed"},"tags":[]}},{"id":"3b1dd2b9","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:21.108521Z","iopub.status.busy":"2025-07-18T06:49:21.108161Z","iopub.status.idle":"2025-07-18T06:49:21.181269Z","shell.execute_reply":"2025-07-18T06:49:21.180694Z"},"papermill":{"duration":0.078055,"end_time":"2025-07-18T06:49:21.182352","exception":false,"start_time":"2025-07-18T06:49:21.104297","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"17d11da4","cell_type":"markdown","source":"## 画像の前処理クラス\n","metadata":{"papermill":{"duration":0.00313,"end_time":"2025-07-18T06:49:21.188935","exception":false,"start_time":"2025-07-18T06:49:21.185805","status":"completed"},"tags":[]}},{"id":"0bfb273f","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:21.196767Z","iopub.status.busy":"2025-07-18T06:49:21.196162Z","iopub.status.idle":"2025-07-18T06:49:21.201312Z","shell.execute_reply":"2025-07-18T06:49:21.200594Z"},"papermill":{"duration":0.010231,"end_time":"2025-07-18T06:49:21.202386","exception":false,"start_time":"2025-07-18T06:49:21.192155","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"da3e21a7","cell_type":"markdown","source":"## データセットクラス","metadata":{"papermill":{"duration":0.003062,"end_time":"2025-07-18T06:49:21.208696","exception":false,"start_time":"2025-07-18T06:49:21.205634","status":"completed"},"tags":[]}},{"id":"6670fda0","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:21.217012Z","iopub.status.busy":"2025-07-18T06:49:21.216795Z","iopub.status.idle":"2025-07-18T06:49:21.221389Z","shell.execute_reply":"2025-07-18T06:49:21.220810Z"},"papermill":{"duration":0.009669,"end_time":"2025-07-18T06:49:21.222585","exception":false,"start_time":"2025-07-18T06:49:21.212916","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"33ecfb43","cell_type":"markdown","source":"## データをリセット\nコマンドで初期化させる\n","metadata":{"papermill":{"duration":0.003108,"end_time":"2025-07-18T06:49:21.228931","exception":false,"start_time":"2025-07-18T06:49:21.225823","status":"completed"},"tags":[]}},{"id":"156c9865","cell_type":"code","source":"!rm -rf ./data\n!rm -f ./best_model.pth","metadata":{"execution":{"iopub.execute_input":"2025-07-18T06:49:21.236631Z","iopub.status.busy":"2025-07-18T06:49:21.235983Z","iopub.status.idle":"2025-07-18T06:49:21.496841Z","shell.execute_reply":"2025-07-18T06:49:21.495803Z"},"papermill":{"duration":0.266193,"end_time":"2025-07-18T06:49:21.498315","exception":false,"start_time":"2025-07-18T06:49:21.232122","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"2ba92912","cell_type":"markdown","source":"## データの解凍とラベル付け\n読み込み→解凍→ラベルつけ→学習・検証データを8:2で分割","metadata":{"papermill":{"duration":0.002955,"end_time":"2025-07-18T06:49:21.504660","exception":false,"start_time":"2025-07-18T06:49:21.501705","status":"completed"},"tags":[]}},{"id":"88411ec4","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:21.511772Z","iopub.status.busy":"2025-07-18T06:49:21.511522Z","iopub.status.idle":"2025-07-18T06:49:45.181427Z","shell.execute_reply":"2025-07-18T06:49:45.180620Z"},"papermill":{"duration":23.675303,"end_time":"2025-07-18T06:49:45.182996","exception":false,"start_time":"2025-07-18T06:49:21.507693","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"1c779e2a","cell_type":"code","source":"print(f\"Train: {len(train_list)} 枚\")\nprint(f\"Val:   {len(val_list)} 枚\")\nprint(f\"Test:  {len(test_list)} 枚\")","metadata":{"execution":{"iopub.execute_input":"2025-07-18T06:49:45.190483Z","iopub.status.busy":"2025-07-18T06:49:45.190246Z","iopub.status.idle":"2025-07-18T06:49:45.194675Z","shell.execute_reply":"2025-07-18T06:49:45.194016Z"},"papermill":{"duration":0.009222,"end_time":"2025-07-18T06:49:45.195745","exception":false,"start_time":"2025-07-18T06:49:45.186523","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"06496f76","cell_type":"markdown","source":"## データセットとデータローダの作成","metadata":{"papermill":{"duration":0.00298,"end_time":"2025-07-18T06:49:45.202611","exception":false,"start_time":"2025-07-18T06:49:45.199631","status":"completed"},"tags":[]}},{"id":"1263ee5f","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:45.209829Z","iopub.status.busy":"2025-07-18T06:49:45.209615Z","iopub.status.idle":"2025-07-18T06:49:45.217989Z","shell.execute_reply":"2025-07-18T06:49:45.217222Z"},"papermill":{"duration":0.013219,"end_time":"2025-07-18T06:49:45.218993","exception":false,"start_time":"2025-07-18T06:49:45.205774","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"1542eac1","cell_type":"markdown","source":"## モデルや変数の定義\n中盤までの層の重みを更新しない","metadata":{"papermill":{"duration":0.003127,"end_time":"2025-07-18T06:49:45.225322","exception":false,"start_time":"2025-07-18T06:49:45.222195","status":"completed"},"tags":[]}},{"id":"87b1169b","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:45.232296Z","iopub.status.busy":"2025-07-18T06:49:45.232096Z","iopub.status.idle":"2025-07-18T06:49:46.376594Z","shell.execute_reply":"2025-07-18T06:49:46.375994Z"},"papermill":{"duration":1.149481,"end_time":"2025-07-18T06:49:46.377995","exception":false,"start_time":"2025-07-18T06:49:45.228514","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"690dd7c2","cell_type":"markdown","source":"## 学習\n","metadata":{"papermill":{"duration":0.004062,"end_time":"2025-07-18T06:49:46.386409","exception":false,"start_time":"2025-07-18T06:49:46.382347","status":"completed"},"tags":[]}},{"id":"61c5f547","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":{"execution":{"iopub.execute_input":"2025-07-18T06:49:46.394552Z","iopub.status.busy":"2025-07-18T06:49:46.394309Z","iopub.status.idle":"2025-07-18T07:00:30.696849Z","shell.execute_reply":"2025-07-18T07:00:30.695801Z"},"papermill":{"duration":644.445739,"end_time":"2025-07-18T07:00:30.835770","exception":false,"start_time":"2025-07-18T06:49:46.390031","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"4dee3096","cell_type":"markdown","source":"## ロスと精度の可視化","metadata":{"papermill":{"duration":0.137458,"end_time":"2025-07-18T07:00:31.160527","exception":false,"start_time":"2025-07-18T07:00:31.023069","status":"completed"},"tags":[]}},{"id":"45e34ada","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":{"execution":{"iopub.execute_input":"2025-07-18T07:00:31.436783Z","iopub.status.busy":"2025-07-18T07:00:31.435875Z","iopub.status.idle":"2025-07-18T07:00:31.901282Z","shell.execute_reply":"2025-07-18T07:00:31.900576Z"},"papermill":{"duration":0.606608,"end_time":"2025-07-18T07:00:31.902816","exception":false,"start_time":"2025-07-18T07:00:31.296208","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"id":"78c93d3f","cell_type":"markdown","source":"## 推論\n","metadata":{"papermill":{"duration":0.138188,"end_time":"2025-07-18T07:00:32.180217","exception":false,"start_time":"2025-07-18T07:00:32.042029","status":"completed"},"tags":[]}},{"id":"a1940271","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":{"execution":{"iopub.execute_input":"2025-07-18T07:00:32.459292Z","iopub.status.busy":"2025-07-18T07:00:32.458998Z","iopub.status.idle":"2025-07-18T07:02:10.457270Z","shell.execute_reply":"2025-07-18T07:02:10.456581Z"},"papermill":{"duration":98.139922,"end_time":"2025-07-18T07:02:10.458508","exception":false,"start_time":"2025-07-18T07:00:32.318586","status":"completed"},"tags":[]},"outputs":[],"execution_count":null}]}