{"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":"none","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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-06-28T01:16:22.472271Z","iopub.execute_input":"2025-06-28T01:16:22.472578Z","iopub.status.idle":"2025-06-28T01:16:22.478762Z","shell.execute_reply.started":"2025-06-28T01:16:22.472556Z","shell.execute_reply":"2025-06-28T01:16:22.477866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\nimport os\n\n# エラーの原因となっていたディレクトリパスを、ご提示の正しいパスに修正します。\ninput_dir = '/kaggle/input/dogs-vs-cats-redux-kernels-edition'\n\n# train.zipのパス\ntrain_zip_path = os.path.join(input_dir, 'train.zip')\n# test.zipのパス (test1.zipから修正)\ntest_zip_path = os.path.join(input_dir, 'test.zip')\n\n# --- train.zipを解凍 ---\nprint(f\"Extracting {train_zip_path}...\")\ntry:\n    with zipfile.ZipFile(train_zip_path, 'r') as zip_ref:\n        zip_ref.extractall('./') # カレントディレクトリに'train'フォルダが作成される\n    print(\"train.zip extracted successfully.\")\nexcept FileNotFoundError:\n    print(f\"ERROR: train.zip not found at the specified path: {train_zip_path}\")\n    print(\"Please make sure the 'input_dir' variable is correct.\")\n\n# --- test.zipを解凍 ---\nprint(f\"Extracting {test_zip_path}...\")\ntry:\n    with zipfile.ZipFile(test_zip_path, 'r') as zip_ref:\n        zip_ref.extractall('./') # カレントディレクトリに'test'フォルダが作成される\n    print(\"test.zip extracted successfully.\")\nexcept FileNotFoundError:\n    print(f\"ERROR: test.zip not found at the specified path: {test_zip_path}\")\n    print(\"Please make sure the 'input_dir' variable is correct.\")\n\nprint(\"\\nUnzipping process finished.\")\n\n# 解凍後のファイルを確認（オプション）\nprint(\"\\nFiles in current directory:\")\n# ls -l コマンドで詳細を表示してみます\n!ls -l ./","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-28T01:16:36.586551Z","iopub.execute_input":"2025-06-28T01:16:36.586884Z","iopub.status.idle":"2025-06-28T01:16:55.027055Z","shell.execute_reply.started":"2025-06-28T01:16:36.586860Z","shell.execute_reply":"2025-06-28T01:16:55.025962Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import transforms\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom PIL import Image\nimport os\n\n# --- 1. ファイルリストとラベルの作成 ---\n# 解凍した'train'フォルダのパス\ntrain_dir = './train'\n# os.listdirでフォルダ内の全ファイル名を取得\ntrain_files = os.listdir(train_dir)\n# 'dog.xxxx.jpg'なら1, 'cat.xxxx.jpg'なら0というラベルを作成\n# ファイル名に'dog'が含まれているかで判定\nlabels = [1 if 'dog' in file else 0 for file in train_files]\n\n# フルパスのリストを作成\ntrain_filepaths = [os.path.join(train_dir, f) for f in train_files]\n\n\n# --- 2. 学習データと検証データに分割 ---\n# 80%を学習用、20%を検証用に分割する\ntrain_filepaths, val_filepaths, train_labels, val_labels = train_test_split(\n    train_filepaths,\n    labels,\n    test_size=0.2,       # 20%を検証用にする\n    random_state=42,     # 実行結果を再現可能にするための乱数シード\n    stratify=labels      # 元のデータセットの犬と猫の比率を保ったまま分割する\n)\n\nprint(f\"Number of training samples: {len(train_filepaths)}\")\nprint(f\"Number of validation samples: {len(val_filepaths)}\")\n\n\n# --- 3. 画像の前処理とデータ拡張を定義 ---\n# ResNetなどの事前学習モデルを使う場合、ImageNetの学習に使われた平均と標準偏差で正規化するのが一般的です\nimage_transforms = {\n    'train': transforms.Compose([\n        transforms.Resize((224, 224)),      # 画像サイズを224x224に統一\n        transforms.RandomHorizontalFlip(),  # 50%の確率で水平反転させる\n        transforms.RandomRotation(10),      # -10度から+10度の範囲でランダムに回転させる\n        transforms.ToTensor(),              # PIL ImageをPyTorchテンソルに変換\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # ImageNetの平均と標準偏差で正規化\n    ]),\n    # 検証データにはデータ拡張（ランダムな変換）は行わない\n    'val': transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n}\n\n\n# --- 4. DatasetとDataLoaderの作成 ---\n# PyTorchのDatasetクラスを継承して、自作のデータセットクラスを作成\nclass CatsDogsDataset(Dataset):\n    def __init__(self, filepaths, labels, 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        # 画像を読み込む\n        img_path = self.filepaths[idx]\n        image = Image.open(img_path).convert('RGB') # JPGなのでRGBに変換\n        # ラベルを取得\n        label = self.labels[idx]\n        \n        # もしtransformが定義されていれば、画像に適用する\n        if self.transform:\n            image = self.transform(image)\n            \n        return image, label\n\n# Datasetオブジェクトを作成\ntrain_dataset = CatsDogsDataset(train_filepaths, train_labels, transform=image_transforms['train'])\nval_dataset = CatsDogsDataset(val_filepaths, val_labels, transform=image_transforms['val'])\n\n# DataLoaderを作成\n# BATCH_SIZEは、一度にモデルに渡す画像の枚数。CPU環境なので大きすぎない値（例: 16や32）が良い\nBATCH_SIZE = 32\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)\n\nprint(\"\\nDataset and DataLoader have been created successfully.\")\nprint(\"Now you are ready to build and train the model.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-28T01:18:15.500280Z","iopub.execute_input":"2025-06-28T01:18:15.500640Z","iopub.status.idle":"2025-06-28T01:18:16.547480Z","shell.execute_reply.started":"2025-06-28T01:18:15.500610Z","shell.execute_reply":"2025-06-28T01:18:16.546234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import models\nimport time # 時間を計測するためインポート\n\n# --- 0. デバイスの設定 ---\n# GPUが利用可能ならGPUを、そうでなければCPUを使用します\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n\n# --- 1. 事前学習済みモデル(ResNet)のロード ---\n# ここではResNet50を使います。'weights'引数で学習済みの重みを指定します。\nmodel = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n\n\n# --- 2. モデルの改造（転移学習のキモ） ---\n# (A) まずは、ダウンロードしたモデルの重みを更新しないように凍結します\nfor param in model.parameters():\n    param.requires_grad = False\n\n# (B) ResNetの最後の全結合層(fc)を、新しい層に差し替えます\n#     - model.fc.in_featuresで、最後の層への入力ユニット数を取得できます\n#     - 出力は「犬」と「猫」の2つなので、2にします\nnum_ftrs = model.fc.in_features\nmodel.fc = nn.Linear(num_ftrs, 2)\n\n# モデル全体を、設定したデバイス（CPUまたはGPU）に送ります\nmodel = model.to(device)\n\nprint(\"Pre-trained model loaded and final layer replaced for 2-class classification.\")\n\n\n# --- 3. 損失関数とオプティマイザの定義 ---\n# 損失関数：分類問題で一般的に使われるクロスエントロピー損失\ncriterion = nn.CrossEntropyLoss()\n\n# オプティマイザ：差し替えた層(model.fc)のパラメータだけを学習対象とします\n# Adamという効率的なアルゴリズムを使用します\noptimizer = optim.Adam(model.fc.parameters(), lr=0.001)\n\n\n# --- 4. 学習ループの実行 ---\nNUM_EPOCHS = 3 # CPUなのでエポック数は少なめに設定（時間はかかります）\nbest_val_acc = 0.0\n\nfor epoch in range(NUM_EPOCHS):\n    epoch_start_time = time.time() # エポック開始時間\n    print(f\"\\nEpoch {epoch+1}/{NUM_EPOCHS}\")\n    print(\"-\" * 10)\n\n    # --- 学習フェーズ ---\n    model.train() # モデルを訓練モードに\n    running_loss = 0.0\n    running_corrects = 0\n\n    # train_loaderからミニバッチ（32枚ずつのデータ）を取り出す\n    for inputs, labels in train_loader:\n        inputs = inputs.to(device)\n        labels = labels.to(device)\n\n        # 勾配をリセット\n        optimizer.zero_grad()\n\n        # 順伝播（モデルに画像を入力して予測を出力）\n        outputs = model(inputs)\n        _, preds = torch.max(outputs, 1) # 最も確率の高いクラスを予測結果とする\n        loss = criterion(outputs, labels) # 損失を計算\n\n        # 逆伝播（損失を元に勾配を計算）\n        loss.backward()\n        # パラメータを更新\n        optimizer.step()\n\n        # 統計情報を記録\n        running_loss += loss.item() * inputs.size(0)\n        running_corrects += torch.sum(preds == labels.data)\n\n    train_epoch_loss = running_loss / len(train_dataset)\n    train_epoch_acc = running_corrects.double() / len(train_dataset)\n\n    print(f'Train Loss: {train_epoch_loss:.4f} Acc: {train_epoch_acc:.4f}')\n\n    # --- 検証フェーズ ---\n    model.eval() # モデルを評価モードに\n    val_loss = 0.0\n    val_corrects = 0\n\n    # 勾配計算は不要なので、torch.no_grad()で囲む\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            _, preds = torch.max(outputs, 1)\n            loss = criterion(outputs, labels)\n\n            val_loss += loss.item() * inputs.size(0)\n            val_corrects += torch.sum(preds == labels.data)\n            \n    val_epoch_loss = val_loss / len(val_dataset)\n    val_epoch_acc = val_corrects.double() / len(val_dataset)\n    \n    epoch_end_time = time.time() # エポック終了時間\n    epoch_duration = epoch_end_time - epoch_start_time #かかった時間\n\n    print(f'Validation Loss: {val_epoch_loss:.4f} Acc: {val_epoch_acc:.4f}')\n    print(f\"Epoch time: {epoch_duration:.2f} seconds\") # 時間を表示\n    \n    # 最高の検証精度が出たモデルの重みを保存\n    if val_epoch_acc > best_val_acc:\n        best_val_acc = val_epoch_acc\n        # 'best_model.pth'という名前でモデルのパラメータを保存\n        torch.save(model.state_dict(), 'best_model.pth')\n        print(\"Best model weights saved.\")\n        \nprint(f\"\\nTraining complete. Best validation accuracy was: {best_val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-28T01:19:19.088130Z","iopub.execute_input":"2025-06-28T01:19:19.088659Z","iopub.status.idle":"2025-06-28T04:36:19.833240Z","shell.execute_reply.started":"2025-06-28T01:19:19.088632Z","shell.execute_reply":"2025-06-28T04:36:19.830851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport pandas as pd\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import models, transforms\nfrom PIL import Image\nimport os\nimport numpy as np\n\n# --- 0. デバイスの設定 ---\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device} for inference\")\n\n\n# --- 1. モデルの準備 ---\n# まずは学習時と全く同じ構造のモデルを準備します\nmodel = models.resnet50(weights=None) # 学習済みの重みは使わないのでNone\nnum_ftrs = model.fc.in_features\nmodel.fc = nn.Linear(num_ftrs, 2)\n\n# 保存しておいた最高のモデルの重み(state_dict)を読み込みます\nmodel.load_state_dict(torch.load('best_model.pth'))\n\n# モデルをデバイスに送り、評価モードに切り替えます\nmodel = model.to(device)\nmodel.eval()\nprint(\"Best model loaded for inference.\")\n\n\n# --- 2. テストデータの準備 ---\n# テストデータ用の前処理を定義します（検証データと全く同じでOK）\ntest_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\n# テストデータ用のDatasetクラス\nclass TestDataset(Dataset):\n    def __init__(self, filepaths, transform=None):\n        self.filepaths = filepaths\n        self.transform = transform\n    def __len__(self):\n        return len(self.filepaths)\n    def __getitem__(self, idx):\n        img_path = self.filepaths[idx]\n        image = Image.open(img_path).convert('RGB')\n        if self.transform:\n            image = self.transform(image)\n        return image, os.path.basename(img_path)\n\n# テストファイルのパスを取得\ntest_dir = './test'\ntest_files = os.listdir(test_dir)\ntest_filepaths = [os.path.join(test_dir, f) for f in test_files]\n\n# DatasetとDataLoaderを作成\ntest_dataset = TestDataset(test_filepaths, transform=test_transform)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)\n\n\n# --- 3. 推論の実行 ---\npredictions = []\nfilenames = []\n\nwith torch.no_grad(): # 勾配計算は不要\n    for inputs, fnames in test_loader:\n        inputs = inputs.to(device)\n        outputs = model(inputs)\n        _, preds = torch.max(outputs, 1) # 最も確率の高いクラスを取得 (0=cat, 1=dog)\n        \n        predictions.extend(preds.cpu().numpy())\n        filenames.extend(fnames)\n\nprint(\"Inference complete.\")\n\n\n# --- 4. 提出ファイルの作成 ---\n# ファイル名からidを取得 (例: '123.jpg' -> 123)\nids = [int(os.path.splitext(fname)[0]) for fname in filenames]\n\n# idと予測結果を紐づけてDataFrameを作成\nsubmission_df = pd.DataFrame({'id': ids, 'label': predictions})\n\n# idの昇順に並べ替える (Kaggleの要件)\nsubmission_df = submission_df.sort_values(by='id')\n\n# CSVファイルとして出力\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"\\nSubmission file 'submission.csv' has been created successfully!\")\nprint(\"You can now submit this file to the Kaggle competition.\")\n# 最初の5行を表示して確認\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-28T04:39:07.042697Z","iopub.execute_input":"2025-06-28T04:39:07.043682Z","iopub.status.idle":"2025-06-28T05:10:38.347854Z","shell.execute_reply.started":"2025-06-28T04:39:07.043570Z","shell.execute_reply":"2025-06-28T05:10:38.346143Z"}},"outputs":[],"execution_count":null}]}