{"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":31040,"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-25T03:57:00.458229Z","iopub.execute_input":"2025-07-25T03:57:00.458479Z","iopub.status.idle":"2025-07-25T03:57:02.208877Z","shell.execute_reply.started":"2025-07-25T03:57:00.458459Z","shell.execute_reply":"2025-07-25T03:57:02.207944Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## モジュールのインポート","metadata":{}},{"cell_type":"code","source":"import os, glob, copy, zipfile\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split\nimport torch\nimport torch.nn as nn\nimport torch.utils.data as data\nimport torch.nn.functional as F\nfrom torchvision import models, transforms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:02.210960Z","iopub.execute_input":"2025-07-25T03:57:02.211342Z","iopub.status.idle":"2025-07-25T03:57:11.549480Z","shell.execute_reply.started":"2025-07-25T03:57:02.211285Z","shell.execute_reply":"2025-07-25T03:57:11.548810Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## パラメータ設定","metadata":{}},{"cell_type":"code","source":"mean = (0.485, 0.456, 0.406) #ImageNetデータセットの平均値\nstd = (0.229, 0.224, 0.225)  #ImageNetデータセットの標準偏差\nbatch_size = 32  #バッチサイズ\nlr = 0.001  #学習率\nepochs = 10 #エポック数(この値を小さくすると実行時間を短縮できます)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:11.550177Z","iopub.execute_input":"2025-07-25T03:57:11.550530Z","iopub.status.idle":"2025-07-25T03:57:11.554903Z","shell.execute_reply.started":"2025-07-25T03:57:11.550511Z","shell.execute_reply":"2025-07-25T03:57:11.554028Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データの用意","metadata":{}},{"cell_type":"code","source":"# データを初期化\n!rm -rf ../data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:11.556024Z","iopub.execute_input":"2025-07-25T03:57:11.556403Z","iopub.status.idle":"2025-07-25T03:57:11.708436Z","shell.execute_reply.started":"2025-07-25T03:57:11.556373Z","shell.execute_reply":"2025-07-25T03:57:11.707609Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# zipファイルのディレクトリ\ndir_zip = '../input/dogs-vs-cats-redux-kernels-edition'\n# データを格納するディレクトリを作成\nos.makedirs('../data', exist_ok=True)\n\n# 学習データのzipファイルを展開\nwith zipfile.ZipFile(os.path.join(dir_zip, 'train.zip')) as train_zip:\n    train_zip.extractall('../data')\n    \n# 推論データのzipファイルを展開\nwith zipfile.ZipFile(os.path.join(dir_zip, 'test.zip')) as test_zip:\n    test_zip.extractall('../data')\n\n# 展開されたディレクトリから画像データのリストを作成\ntrain_list = glob.glob(os.path.join('../data/train', '*.jpg'))\ntest_list = glob.glob(os.path.join('../data/test', '*.jpg'))\n\n# 学習データから検証データを分割(8:2)\ntrain_list, val_list = train_test_split(train_list, test_size=0.2)\n\nprint(f'Train Data:{len(train_list)}')\nprint(f'Validation Data:{len(val_list)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:11.709532Z","iopub.execute_input":"2025-07-25T03:57:11.709810Z","iopub.status.idle":"2025-07-25T03:57:26.792144Z","shell.execute_reply.started":"2025-07-25T03:57:11.709778Z","shell.execute_reply":"2025-07-25T03:57:26.791353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# データの前処理の機能\n\n# 学習データ（画像サイズ変換、ランダム反転、テンソル変換、正規化）\ntrain_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])\n\n# 推論データ（画像サイズ変換、テンソル変換、正規化）\nval_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:26.793157Z","iopub.execute_input":"2025-07-25T03:57:26.793489Z","iopub.status.idle":"2025-07-25T03:57:26.799979Z","shell.execute_reply.started":"2025-07-25T03:57:26.793466Z","shell.execute_reply":"2025-07-25T03:57:26.798353Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 画像データにラベル(0,1)を付ける機能\n\nclass CatDogDataset(data.Dataset):\n    # 初期化\n    def __init__(self, file_list, transform=None):\n        self.file_list = file_list\n        self.transform = transform\n    \n    # データセットのサイズ\n    def __len__(self):\n        return len(self.file_list)\n    \n    # ラベル付け\n    def __getitem__(self, idx):\n        img_path = self.file_list[idx]\n        img = Image.open(img_path)\n        img_transformed = self.transform(img)\n        \n        # ファイル名からラベルを取得\n        label = img_path.split('/')[-1].split('.')[0]\n        if label == 'dog': #ファイル名がdogであれば 1\n            label = 1\n        elif label == 'cat':#ファイル名がcatであれば 0\n            label = 0\n\n        return img_transformed, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:26.802427Z","iopub.execute_input":"2025-07-25T03:57:26.802683Z","iopub.status.idle":"2025-07-25T03:57:27.036745Z","shell.execute_reply.started":"2025-07-25T03:57:26.802664Z","shell.execute_reply":"2025-07-25T03:57:27.035802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# データの前処理\ntrain_dataset = CatDogDataset(train_list, transform=train_transforms)\nval_dataset = CatDogDataset(val_list, transform=val_transforms)\n\n# データの読み込み\ntrain_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader = data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:27.037660Z","iopub.execute_input":"2025-07-25T03:57:27.037899Z","iopub.status.idle":"2025-07-25T03:57:27.057144Z","shell.execute_reply.started":"2025-07-25T03:57:27.037881Z","shell.execute_reply":"2025-07-25T03:57:27.056226Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## モデルの用意","metadata":{}},{"cell_type":"code","source":"# ResNet-18の事前学習済みモデル\nweights = models.ResNet18_Weights.DEFAULT\nmodel = models.resnet18(weights=weights)\n    \n# モデルの全結合層を追加して2クラス分類に変更\nnum_classes = 2\nmodel.fc = nn.Linear(model.fc.in_features, num_classes)\n\n# パラメータを更新する/固定する層を決定\nupdate_params = 'layer4'  #更新したい層の名前（タプルで複数指定しても可）\nfor name, param in model.named_parameters():\n    if name.startswith(update_params):\n        param.requires_grad = True\n    else:\n        param.requires_grad = False\n\n# GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)\n\n# 損失関数\ncriterion = nn.CrossEntropyLoss() #クロスエントロピー\n# 最適化手法\nparam_list = list(filter(lambda p: p.requires_grad, model.parameters()))\noptimizer = torch.optim.Adam(param_list, lr=lr) #更新する層だけ指定","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:27.058134Z","iopub.execute_input":"2025-07-25T03:57:27.058505Z","iopub.status.idle":"2025-07-25T03:57:27.961185Z","shell.execute_reply.started":"2025-07-25T03:57:27.058485Z","shell.execute_reply":"2025-07-25T03:57:27.960527Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 学習","metadata":{}},{"cell_type":"code","source":"#学習曲線描画のため、ログを取得\ntrain_acc_list = []\nval_acc_list = []\ntrain_loss_list = []\nval_loss_list = []\n\n# 最良モデルと精度の初期化\nbest_model = copy.deepcopy(model.state_dict())\nbest_accuracy = 0.0\n\n# 学習\nfor epoch in range(epochs):\n    model.train()\n    epoch_loss = 0\n    epoch_accuracy = 0\n\n    for data, label in tqdm(train_loader):\n        \n        data = data.to(device)\n        label = label.to(device)\n\n        output = model(data)\n        loss = criterion(output, label)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        acc = (output.argmax(dim=1) == label).float().mean()\n        epoch_accuracy  += acc\n        epoch_loss      += loss.item()\n    epoch_accuracy  /= len(train_loader)\n    epoch_loss      /= len(train_loader)\n\n    with torch.no_grad():\n        model.eval()\n        epoch_val_accuracy = 0\n        epoch_val_loss = 0\n\n        for data, label in val_loader:\n            data = data.to(device)\n            label = label.to(device)\n\n            val_output = model(data)\n            val_loss = criterion(val_output, label)\n\n            acc = (val_output.argmax(dim=1) == label).float().mean()\n            epoch_val_accuracy  += acc\n            epoch_val_loss      += val_loss.item()\n        epoch_val_accuracy  /= len(val_loader)\n        epoch_val_loss      /= len(val_loader)\n    \n    print(\n        f\"Epoch : {epoch+1} - loss : {epoch_loss:.4f} - acc: {epoch_accuracy:.4f} - val_loss : {epoch_val_loss:.4f} - val_acc: {epoch_val_accuracy:.4f}\\n\"\n    )\n    # 精度が最も良いならモデルを保存\n    if epoch_val_accuracy > best_accuracy:\n        best_accuracy = epoch_val_accuracy\n        best_model = copy.deepcopy(model.state_dict())\n\n    train_acc_list.append(epoch_accuracy.cpu().item())\n    val_acc_list.append(epoch_val_accuracy.cpu().item())\n    train_loss_list.append(epoch_loss)\n    val_loss_list.append(epoch_val_loss)\n\ntorch.save(best_model, '../working/model.pth')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T03:57:27.962003Z","iopub.execute_input":"2025-07-25T03:57:27.962346Z","iopub.status.idle":"2025-07-25T04:15:03.058751Z","shell.execute_reply.started":"2025-07-25T03:57:27.962298Z","shell.execute_reply":"2025-07-25T04:15:03.058075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# グラフ描画\nplt.figure(figsize=(12, 5))\n\n# Loss の描画\nplt.subplot(1, 2, 1)\nplt.plot(range(1, epochs+1),train_loss_list, 'bo-', label='Train Loss')\nplt.plot(range(1, epochs+1),val_loss_list, 'ro-', label='Validation Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss over Epochs')\nplt.legend()\n\n# Accuracy の描画\nplt.subplot(1, 2, 2)\nplt.plot(range(1, epochs+1),train_acc_list, 'bo-', label='Train Accuracy')\nplt.plot(range(1, epochs+1),val_acc_list, 'ro-', label='Validation Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Accuracy over Epochs')\nplt.legend()\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T04:15:03.059736Z","iopub.execute_input":"2025-07-25T04:15:03.060040Z","iopub.status.idle":"2025-07-25T04:15:03.564850Z","shell.execute_reply.started":"2025-07-25T04:15:03.060013Z","shell.execute_reply":"2025-07-25T04:15:03.564121Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 推論","metadata":{}},{"cell_type":"code","source":"param = torch.load('../working/model.pth')\nmodel.load_state_dict(param)\nmodel = model.eval()\n\n# IDと予測値の格納用リスト\nid_list = []\npred_list = []\n\nwith torch.no_grad():\n    for test_path in tqdm(test_list):\n        # 画像とIDの取得\n        img = Image.open(test_path)\n        id_number = int(test_path.split('/')[-1].split('.')[0])\n        \n        # 推論用に画像データを変換\n        img = val_transforms(img)\n        img = img.unsqueeze(0)\n        img = img.to(device)\n\n        outputs = model(img)\n        preds = F.softmax(outputs, dim=1)[:, 1].tolist() #dogの確率\n\n        # IDと予測値をリストに追加\n        id_list.append(id_number)\n        pred_list.append(preds[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T04:15:03.565764Z","iopub.execute_input":"2025-07-25T04:15:03.566078Z","iopub.status.idle":"2025-07-25T04:16:23.611661Z","shell.execute_reply.started":"2025-07-25T04:15:03.566058Z","shell.execute_reply":"2025-07-25T04:16:23.610771Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 提出用csvファイルの作成","metadata":{}},{"cell_type":"code","source":"# サンプルファイルを利用\nsubmit = pd.read_csv(dir_zip + '/sample_submission.csv')\n\nsubmit.set_index('id', inplace=True)\nsubmit.loc[id_list, 'label'] = pred_list\nsubmit.reset_index(inplace=True)\n\nsubmit.to_csv('/kaggle/working/submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T04:16:23.612670Z","iopub.execute_input":"2025-07-25T04:16:23.612930Z","iopub.status.idle":"2025-07-25T04:16:23.688437Z","shell.execute_reply.started":"2025-07-25T04:16:23.612911Z","shell.execute_reply":"2025-07-25T04:16:23.687756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(submit)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T04:16:23.689091Z","iopub.execute_input":"2025-07-25T04:16:23.689322Z","iopub.status.idle":"2025-07-25T04:16:23.702004Z","shell.execute_reply.started":"2025-07-25T04:16:23.689292Z","shell.execute_reply":"2025-07-25T04:16:23.701371Z"}},"outputs":[],"execution_count":null}]}