{"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-11T07:43:23.285458Z","iopub.execute_input":"2025-07-11T07:43:23.285765Z","iopub.status.idle":"2025-07-11T07:43:23.291524Z","shell.execute_reply.started":"2025-07-11T07:43:23.285744Z","shell.execute_reply":"2025-07-11T07:43:23.290747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# モジュールのインポート\nimport os\nimport glob\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport torch.nn as nn\nimport torch.optim as optim","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:43:23.292882Z","iopub.execute_input":"2025-07-11T07:43:23.293146Z","iopub.status.idle":"2025-07-11T07:43:23.311570Z","shell.execute_reply.started":"2025-07-11T07:43:23.293124Z","shell.execute_reply":"2025-07-11T07:43:23.310868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#データセット作成\nclass Dataset(Dataset):\n    def __init__(self, filepaths, labels=None, transform=None):\n        self.filepaths = filepaths\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.filepaths)\n\n    def __getitem__(self, idx):\n        img_path = self.filepaths[idx]\n        img = Image.open(img_path).convert('RGB')\n        if self.transform:\n            img = self.transform(img)\n        if self.labels is not None:\n            label = self.labels[idx]\n            return img, label\n        else:\n            return img, os.path.basename(img_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:43:23.312802Z","iopub.execute_input":"2025-07-11T07:43:23.313057Z","iopub.status.idle":"2025-07-11T07:43:23.325026Z","shell.execute_reply.started":"2025-07-11T07:43:23.313039Z","shell.execute_reply":"2025-07-11T07:43:23.324244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#zipファイル展開\nimport zipfile\nimport os\n\n#　zipファイルのパス\nzip_dir = \"../input/dogs-vs-cats-redux-kernels-edition/\"\noutput_dir = \"/kaggle/working/\"\n\n#解凍対象のzipファイル一覧\nzip_files = [\"train.zip\", \"test.zip\"]\n\n#各zipファイルを展開\nfor zip_file in zip_files:\n    zip_path = os.path.join(zip_dir, zip_file)\n    with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n        zip_ref.extractall(output_dir)\n        \n#展開後のパスを設定\ntrain_dir = os.path.join(output_dir, \"train\")\ntest_dir = os.path.join(output_dir, \"test\")\n\n#画像ファイルを取得\ntrain_files = glob.glob(os.path.join(train_dir, \"*.jpg\"))\ntest_files = glob.glob(os.path.join(test_dir, \"*.jpg\"))\nprint(f\"ファイル数: {len(train_files)}\")\nprint(f\"ファイル数: {len(test_files)}\")\nlabels = [1 if \"dog\" in os.path.basename(f) else 0 for f in train_files]\n\nprint(f\"train_files: {len(train_files)}\")\nprint(f\"labels: {len(labels)}\")\n\n#train/val分割\ntrain_files, val_files, train_labels, val_labels = train_test_split(\n    train_files, labels, test_size=0.2, random_state=42, stratify=labels\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:43:23.325884Z","iopub.execute_input":"2025-07-11T07:43:23.326259Z","iopub.status.idle":"2025-07-11T07:43:32.901090Z","shell.execute_reply.started":"2025-07-11T07:43:23.326235Z","shell.execute_reply":"2025-07-11T07:43:32.900314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 前処理\ntransform = transforms.Compose([\n    transforms.Resize(232),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406], \n        std=[0.229, 0.224, 0.225]\n    )\n])\n\n#DataLoaderの作成\nbatch_size = 32\n\ntrain_dataset = Dataset(train_files, train_labels, transform)\nval_dataset = Dataset(val_files, val_labels, transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=batch_size)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:43:32.902721Z","iopub.execute_input":"2025-07-11T07:43:32.902931Z","iopub.status.idle":"2025-07-11T07:43:32.909297Z","shell.execute_reply.started":"2025-07-11T07:43:32.902914Z","shell.execute_reply":"2025-07-11T07:43:32.908529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# モデル構築・学習準備\nfrom torchvision.models import resnet18, ResNet18_Weights\n#from torch.optim.lr_scheduler import StepLR\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nweights=ResNet18_Weights.IMAGENET1K_V1\nmodel = resnet18(weights=weights)\n\n# conv1とbn1を固定\n#for param in model.conv1.parameters():\n#    param.requires_grad = False\n# layer1とlayer2も固定\n#for param in model.bn1.parameters():\n#    param.requires_grad = False\n\n#for param in model.layer1.parameters():\n#    param.requires_grad = False\n\n#for param in model.layer2.parameters():\n#    param.requires_grad = False\nmodel.fc = nn.Linear(model.fc.in_features, 2)\n\n# transformを定義\ntransform = weights.transforms()\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\n#optimizer = optim.Adam(model.fc.parameters(), lr=1e-4)\n# 残りの層だけoptimizerに渡す(学習率を調整)\noptimizer = torch.optim.Adam([\n    #{\"params\": model.layer1.parameters(), \"lr\": 1e-6},\n    #{\"params\": model.layer2.parameters(), \"lr\": 1e-5},\n    #{\"params\": model.layer3.parameters(), \"lr\": 1e-7},\n    {\"params\": model.layer4.parameters(), \"lr\": 1e-5},\n    {\"params\": model.fc.parameters(),     \"lr\": 1e-4}\n    ], weight_decay=1e-4)\n\n#scheduler = StepLR(optimizer, step_size=5, gamma=0.1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:43:32.910014Z","iopub.execute_input":"2025-07-11T07:43:32.910188Z","iopub.status.idle":"2025-07-11T07:43:33.158888Z","shell.execute_reply.started":"2025-07-11T07:43:32.910174Z","shell.execute_reply":"2025-07-11T07:43:33.158283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom tqdm import tqdm\n\n#num_epochs = 5\n#num_epochs = 10\n\n# version9\nnum_epochs = 30\n#patience = 5\nbest_val_loss = float('inf')\nepochs_no_improve = 0\ndelta = 1e-4\n\n#version10\n#patience = 3\n#version11\n#patience = 5\n#version12\npatience = 3\n\ntrain_losses = []\nval_losses = []\ntrain_accuracies = []\nval_accuracies = []\n\nfor epoch in range(num_epochs):\n    model.train()  # 学習モード\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in tqdm(train_loader):\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n\n        # 精度計算\n        _, predicted = torch.max(outputs, 1)\n        correct += (predicted == labels).sum().item()\n        total += labels.size(0)\n        \n    train_epoch_loss = running_loss / total\n    train_epoch_acc = correct / total\n    \n    train_losses.append(train_epoch_loss)\n    train_accuracies.append(train_epoch_acc)\n    \n    print(f\"[{epoch+1}/{num_epochs}] Loss: {train_epoch_loss:.4f} | Accuracy: {train_epoch_acc:.4f}\")\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 images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n            # 予測ラベルを取得\n            _, preds = torch.max(outputs, 1)\n\n            val_loss += loss.item() * images.size(0)  # 合計loss\n            val_correct += (preds == labels).sum().item()\n            val_total += labels.size(0)\n            \n    val_epoch_loss = val_loss / val_total\n    val_epoch_acc = val_correct / val_total\n    \n    val_losses.append(val_epoch_loss)\n    val_accuracies.append(val_epoch_acc)\n\n    print(f\"→ Val Loss: {val_epoch_loss:.4f} | Val Accuracy: {val_epoch_acc:.4f}\")\n\n    # 学習率の調整\n    #scheduler.step()\n\n    # アーリーストッピングの導入\n    if val_epoch_loss < best_val_loss- delta:\n        best_val_loss = val_epoch_loss\n        epochs_no_improve = 0\n    else:\n        epochs_no_improve += 1\n        if epochs_no_improve >= patience:\n            print(f\"Early stopping at epoch {epoch+1}\")\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:43:33.159654Z","iopub.execute_input":"2025-07-11T07:43:33.159904Z","iopub.status.idle":"2025-07-11T07:55:30.491914Z","shell.execute_reply.started":"2025-07-11T07:43:33.159882Z","shell.execute_reply":"2025-07-11T07:55:30.491136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 学習の損失と精度の可視化\nimport matplotlib.pyplot as plt\n\nplt.plot(train_losses, label=\"Train Loss\")\nplt.plot(val_losses, label=\"Val Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Loss Curve\")\nplt.legend()\nplt.show()\n\nplt.plot(train_accuracies, label=\"Train Accuracy\")\nplt.plot(val_accuracies, label=\"Val Accuracy\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"Accuracy Curve\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:55:30.492714Z","iopub.execute_input":"2025-07-11T07:55:30.492993Z","iopub.status.idle":"2025-07-11T07:55:30.828454Z","shell.execute_reply.started":"2025-07-11T07:55:30.492975Z","shell.execute_reply":"2025-07-11T07:55:30.827781Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# transforms は学習と同じものを使用\ntest_transform = transform  \n\n# testデータセット作成\nclass TestDataset(torch.utils.data.Dataset):\n    def __init__(self, file_paths, transform=None):\n        self.file_paths = file_paths\n        self.transform = transform\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        if self.transform:\n            image = self.transform(image)\n        return image, img_path\n\n# testファイル一覧\ntest_dataset = TestDataset(test_files, transform=test_transform)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:55:30.829461Z","iopub.execute_input":"2025-07-11T07:55:30.829830Z","iopub.status.idle":"2025-07-11T07:55:30.835247Z","shell.execute_reply.started":"2025-07-11T07:55:30.829804Z","shell.execute_reply":"2025-07-11T07:55:30.834569Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 推論モード\nmodel.eval()\nall_preds = []\nall_ids = []\n\nwith torch.no_grad():\n    for images, paths in tqdm(test_loader):\n        images = images.to(device)\n        outputs = model(images)\n#        _, preds = torch.max(outputs, 1) # ここで0or1分類しているのでsoftmaxで確率に変更する\n        probs = torch.softmax(outputs, dim=1)\n        preds = probs[:, 1].cpu().numpy() # 犬クラスの確率\n        all_preds.extend(preds)\n        all_ids.extend([int(os.path.basename(p).split(\".\")[0]) for p in paths])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:55:30.835991Z","iopub.execute_input":"2025-07-11T07:55:30.836217Z","iopub.status.idle":"2025-07-11T07:56:24.058632Z","shell.execute_reply.started":"2025-07-11T07:55:30.836202Z","shell.execute_reply":"2025-07-11T07:56:24.057885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#CSVファイル作成\n# idとlabelをDataFrameに\nsubmission_df = pd.DataFrame({\n    \"id\": all_ids,\n    \"label\": all_preds\n})\n\nsubmission_df = submission_df.sort_values(\"id\").reset_index(drop=True)\nsubmission_df.to_csv(\"/kaggle/working/submission.csv\", index=False)\n# チェック\nprint(submission_df.dtypes)\nprint(submission_df.head())\nprint(submission_df[\"label\"].min(), submission_df[\"label\"].max())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T07:56:24.060318Z","iopub.execute_input":"2025-07-11T07:56:24.060565Z","iopub.status.idle":"2025-07-11T07:56:24.113657Z","shell.execute_reply.started":"2025-07-11T07:56:24.060549Z","shell.execute_reply":"2025-07-11T07:56:24.112932Z"}},"outputs":[],"execution_count":null}]}