{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","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":31089,"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-07-18T05:40:00.761466Z","iopub.execute_input":"2025-07-18T05:40:00.761879Z","iopub.status.idle":"2025-07-18T05:40:00.774431Z","shell.execute_reply.started":"2025-07-18T05:40:00.761851Z","shell.execute_reply":"2025-07-18T05:40:00.772971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!/usr/bin/env python3\n\"\"\"\nDogs vs Cats Redux: Kernels Edition - 改良版\nKaggleコンペティション用の画像分類モデル\n\n主な改良点:\nResNet50を使用（元はResNet18）\nデータ拡張の追加\n3. 学習率スケジューラー\n4. 早期停止機能\n5. より詳細な評価とログ出力\n6. モデル保存と可視化\n\"\"\"\n\nimport os\nimport zipfile\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport seaborn as sns\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nimport torchvision.models as models\nfrom torch.optim.lr_scheduler import StepLR\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# ===== 1. データセット解凍 =====\ndef extract_datasets():\n    \"\"\"zipファイルを解凍\"\"\"\n    print(\"📦 データセットの解凍を開始...\")\n    \n    input_dir = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition/\"\n    work_dir = \"/kaggle/working/\"\n    \n    with zipfile.ZipFile(input_dir + \"train.zip\", \"r\") as zip_ref:\n        zip_ref.extractall(work_dir + \"train/\")\n    \n    with zipfile.ZipFile(input_dir + \"test.zip\", \"r\") as zip_ref:\n        zip_ref.extractall(work_dir + \"test/\")\n    \n    print(\"✅ データセットの解凍完了\")\n    return work_dir\n\n# ===== 2. データ統計表示 =====\ndef show_data_info(work_dir):\n    \"\"\"データセットの統計情報を表示\"\"\"\n    train_root = os.path.join(work_dir, \"train\")\n    test_root = os.path.join(work_dir, \"test\")\n    \n    print(\"\\n📊 データセット情報:\")\n    print(f\"📁 train フォルダの内容: {os.listdir(train_root)}\")\n    \n    train_dir = os.path.join(train_root, \"train\")\n    test_dir = os.path.join(test_root, \"test\")\n    \n    all_files = os.listdir(train_dir)\n    cat_count = sum(1 for f in all_files if f.startswith('cat'))\n    dog_count = sum(1 for f in all_files if f.startswith('dog'))\n    \n    print(f\"  - 猫の画像: {cat_count:,}枚\")\n    print(f\"  - 犬の画像: {dog_count:,}枚\")\n    print(f\"  - 総画像数: {len(all_files):,}枚\")\n    print(f\"  - テスト画像数: {len(os.listdir(test_dir)):,}枚\")\n    \n    return train_dir, test_dir\n\n# ===== 3. 設定 =====\nclass Config:\n    \"\"\"モデルとトレーニングの設定\"\"\"\n    BATCH_SIZE = 32\n    EPOCHS = 3\n    IMG_SIZE = 224\n    LR = 0.001\n    EARLY_STOPPING_PATIENCE = 3\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    \n    @classmethod\n    def print_config(cls):\n        print(\"\\n⚙️ 設定:\")\n        print(f\"  - デバイス: {cls.DEVICE}\")\n        print(f\"  - バッチサイズ: {cls.BATCH_SIZE}\")\n        print(f\"  - エポック数: {cls.EPOCHS}\")\n        print(f\"  - 画像サイズ: {cls.IMG_SIZE}x{cls.IMG_SIZE}\")\n        print(f\"  - 学習率: {cls.LR}\")\n\n# ===== 4. Dataset クラス =====\nclass CatsDogsDataset(Dataset):\n    \"\"\"猫と犬の画像データセット\"\"\"\n    \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        try:\n            image = Image.open(self.filepaths[idx]).convert(\"RGB\")\n            if self.transform:\n                image = self.transform(image)\n            if self.labels is not None:\n                return image, self.labels[idx]\n            else:\n                return image\n        except Exception as e:\n            print(f\"Error loading image {self.filepaths[idx]}: {e}\")\n            # エラーの場合は黒い画像を返す\n            if self.transform:\n                image = self.transform(Image.new('RGB', (224, 224), (0, 0, 0)))\n            else:\n                image = Image.new('RGB', (224, 224), (0, 0, 0))\n            if self.labels is not None:\n                return image, self.labels[idx]\n            else:\n                return image\n\n# ===== 5. データ変換 =====\ndef get_transforms():\n    \"\"\"データ拡張の設定\"\"\"\n    # 訓練用：データ拡張あり\n    train_transform = transforms.Compose([\n        transforms.Resize((Config.IMG_SIZE, Config.IMG_SIZE)),\n        transforms.RandomHorizontalFlip(p=0.5),\n        transforms.RandomRotation(degrees=15),\n        transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    # 検証・テスト用：データ拡張なし\n    val_transform = transforms.Compose([\n        transforms.Resize((Config.IMG_SIZE, Config.IMG_SIZE)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    print(\"\\n🎨 データ拡張の設定:\")\n    print(\"  - 訓練用: 水平反転、回転、色調変更\")\n    print(\"  - 検証用: リサイズのみ\")\n    \n    return train_transform, val_transform\n\n# ===== 6. データローダー準備 =====\ndef prepare_data_loaders(train_dir, train_transform, val_transform):\n    \"\"\"データローダーの準備\"\"\"\n    train_files = [os.path.join(train_dir, f) for f in os.listdir(train_dir) \n                   if f.endswith(('.jpg', '.jpeg', '.png'))]\n    labels = [1 if 'dog' in f else 0 for f in train_files]  # 0: cat, 1: dog\n    \n    # 層化サンプリングで訓練・検証データを分割\n    X_train, X_val, y_train, y_val = train_test_split(\n        train_files, labels, test_size=0.15, random_state=42, stratify=labels\n    )\n    \n    print(f\"\\n📊 データセット分割:\")\n    print(f\"  - 訓練データ: {len(X_train):,}枚\")\n    print(f\"  - 検証データ: {len(X_val):,}枚\")\n    print(f\"  - 訓練データの犬: {sum(y_train):,}枚, 猫: {len(y_train) - sum(y_train):,}枚\")\n    print(f\"  - 検証データの犬: {sum(y_val):,}枚, 猫: {len(y_val) - sum(y_val):,}枚\")\n    \n    # Dataset作成\n    train_dataset = CatsDogsDataset(X_train, y_train, train_transform)\n    val_dataset = CatsDogsDataset(X_val, y_val, val_transform)\n    \n    # DataLoader作成\n    train_loader = DataLoader(train_dataset, batch_size=Config.BATCH_SIZE, \n                             shuffle=True, num_workers=2)\n    val_loader = DataLoader(val_dataset, batch_size=Config.BATCH_SIZE, \n                           shuffle=False, num_workers=2)\n    \n    print(f\"\\n✅ DataLoader作成完了\")\n    print(f\"  - 訓練バッチ数: {len(train_loader)}\")\n    print(f\"  - 検証バッチ数: {len(val_loader)}\")\n    \n    return train_loader, val_loader\n\n# ===== 7. モデル準備 =====\ndef create_model():\n    \"\"\"ResNet50モデルの作成\"\"\"\n    model = models.resnet50(weights='IMAGENET1K_V2')\n    \n    # 最後の分類層を2クラス分類用に変更\n    num_features = model.fc.in_features\n    model.fc = nn.Sequential(\n        nn.Dropout(0.5),\n        nn.Linear(num_features, 2)\n    )\n    \n    model = model.to(Config.DEVICE)\n    \n    # 損失関数とオプティマイザー\n    criterion = nn.CrossEntropyLoss()\n    optimizer = torch.optim.AdamW(model.parameters(), lr=Config.LR, weight_decay=1e-4)\n    scheduler = StepLR(optimizer, step_size=2, gamma=0.5)\n    \n    print(f\"\\n🤖 モデル準備完了:\")\n    print(f\"  - アーキテクチャ: ResNet50\")\n    print(f\"  - パラメータ数: {sum(p.numel() for p in model.parameters()):,}\")\n    print(f\"  - 学習可能パラメータ数: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}\")\n    \n    return model, criterion, optimizer, scheduler\n\n# ===== 8. 検証関数 =====\ndef validate_model(model, val_loader, criterion):\n    \"\"\"モデルの検証\"\"\"\n    model.eval()\n    val_loss = 0.0\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(Config.DEVICE), labels.to(Config.DEVICE)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item()\n            \n            _, predicted = torch.max(outputs.data, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n    \n    accuracy = 100 * correct / total\n    avg_loss = val_loss / len(val_loader)\n    \n    return avg_loss, accuracy\n\n# ===== 9. 学習ループ =====\ndef train_model(model, train_loader, val_loader, criterion, optimizer, scheduler):\n    \"\"\"モデルの学習\"\"\"\n    train_losses = []\n    val_losses = []\n    val_accuracies = []\n    best_val_acc = 0\n    patience_counter = 0\n    \n    print(\"\\n🚀 学習開始\")\n    print(\"-\" * 60)\n    \n    for epoch in range(Config.EPOCHS):\n        # 訓練フェーズ\n        model.train()\n        running_loss = 0.0\n        correct = 0\n        total = 0\n        \n        for i, (images, labels) in enumerate(train_loader):\n            images, labels = images.to(Config.DEVICE), labels.to(Config.DEVICE)\n            \n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item()\n            _, predicted = torch.max(outputs.data, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n            \n            # 進捗表示\n            if (i + 1) % 200 == 0:\n                print(f\"Epoch [{epoch+1}/{Config.EPOCHS}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}\")\n        \n        # 検証フェーズ\n        val_loss, val_acc = validate_model(model, val_loader, criterion)\n        \n        # スケジューラーの更新\n        scheduler.step()\n        \n        # 結果の記録\n        train_loss = running_loss / len(train_loader)\n        train_acc = 100 * correct / total\n        \n        train_losses.append(train_loss)\n        val_losses.append(val_loss)\n        val_accuracies.append(val_acc)\n        \n        print(f\"\\n[Epoch {epoch+1}/{Config.EPOCHS}]\")\n        print(f\"  Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%\")\n        print(f\"  Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%\")\n        print(f\"  Learning Rate: {scheduler.get_last_lr()[0]:.6f}\")\n        \n        # 最良モデルの保存\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            torch.save(model.state_dict(), 'best_model.pth')\n            patience_counter = 0\n            print(f\"  ✅ 新しい最良モデルを保存 (Val Acc: {val_acc:.2f}%)\")\n        else:\n            patience_counter += 1\n            if patience_counter >= Config.EARLY_STOPPING_PATIENCE:\n                print(f\"  ⚠️ 早期停止: {Config.EARLY_STOPPING_PATIENCE}エポック改善なし\")\n                break\n        \n        print(\"-\" * 60)\n    \n    print(f\"\\n🎉 学習完了! 最良検証精度: {best_val_acc:.2f}%\")\n    return train_losses, val_losses, val_accuracies, best_val_acc\n\n# ===== 10. 学習結果の可視化 =====\ndef visualize_training(train_losses, val_losses, val_accuracies):\n    \"\"\"学習結果の可視化\"\"\"\n    plt.figure(figsize=(15, 5))\n    \n    # 損失の可視化\n    plt.subplot(1, 3, 1)\n    plt.plot(train_losses, label='Train Loss', marker='o')\n    plt.plot(val_losses, label='Val Loss', marker='s')\n    plt.title('Training and Validation Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.grid(True)\n    \n    # 精度の可視化\n    plt.subplot(1, 3, 2)\n    plt.plot(val_accuracies, label='Val Accuracy', marker='o', color='green')\n    plt.title('Validation Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy (%)')\n    plt.legend()\n    plt.grid(True)\n    \n    # 学習率の可視化\n    plt.subplot(1, 3, 3)\n    lr_history = [Config.LR * (0.5 ** (i // 2)) for i in range(len(train_losses))]\n    plt.plot(lr_history, marker='o', color='red')\n    plt.title('Learning Rate Schedule')\n    plt.xlabel('Epoch')\n    plt.ylabel('Learning Rate')\n    plt.grid(True)\n    plt.yscale('log')\n    \n    plt.tight_layout()\n    plt.savefig('training_history.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    \n    print(\"✅ 学習履歴を training_history.png に保存しました\")\n\n# ===== 11. テストデータでの予測 =====\ndef predict_test_data(model, test_dir, val_transform):\n    \"\"\"テストデータでの予測とsubmission作成\"\"\"\n    # 最良モデルを読み込み\n    model.load_state_dict(torch.load('best_model.pth'))\n    model.eval()\n    \n    # テストデータの準備\n    test_files = [os.path.join(test_dir, f) for f in os.listdir(test_dir) \n                  if f.endswith(('.jpg', '.jpeg', '.png'))]\n    test_files.sort(key=lambda x: int(os.path.basename(x).split('.')[0]))\n    \n    test_dataset = CatsDogsDataset(test_files, None, val_transform)\n    test_loader = DataLoader(test_dataset, batch_size=Config.BATCH_SIZE, \n                            shuffle=False, num_workers=2)\n    \n    # 予測実行\n    predictions = []\n    with torch.no_grad():\n        for images in test_loader:\n            images = images.to(Config.DEVICE)\n            outputs = model(images)\n            probs = F.softmax(outputs, dim=1)\n            dog_probs = probs[:, 1].cpu().numpy()\n            predictions.extend(dog_probs)\n    \n    # Submission作成\n    ids = [int(os.path.basename(f).split('.')[0]) for f in test_files]\n    submission = pd.DataFrame({\n        'id': ids,\n        'label': predictions\n    })\n    \n    submission.to_csv('submission.csv', index=False)\n    \n    print(f\"\\n✅ Submission完了!\")\n    print(f\"  - 予測数: {len(predictions):,}件\")\n    print(f\"  - 犬の確率の統計:\")\n    print(f\"    - 平均: {np.mean(predictions):.3f}\")\n    print(f\"    - 標準偏差: {np.std(predictions):.3f}\")\n    print(f\"    - 最小値: {np.min(predictions):.3f}\")\n    print(f\"    - 最大値: {np.max(predictions):.3f}\")\n    \n    return predictions\n\n# ===== 12. 予測分布の可視化 =====\ndef visualize_predictions(predictions):\n    \"\"\"予測分布の可視化\"\"\"\n    plt.figure(figsize=(10, 4))\n    \n    plt.subplot(1, 2, 1)\n    plt.hist(predictions, bins=50, alpha=0.7, edgecolor='black')\n    plt.title('Prediction Distribution')\n    plt.xlabel('Dog Probability')\n    plt.ylabel('Frequency')\n    plt.grid(True)\n    \n    plt.subplot(1, 2, 2)\n    plt.plot(sorted(predictions))\n    plt.title('Sorted Predictions')\n    plt.xlabel('Sample Index')\n    plt.ylabel('Dog Probability')\n    plt.grid(True)\n    \n    plt.tight_layout()\n    plt.savefig('prediction_distribution.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    \n    print(\"✅ 予測分布を prediction_distribution.png に保存しました\")\n\n# ===== 13. 最終サマリー =====\ndef print_final_summary(best_val_acc, num_epochs):\n    \"\"\"最終結果のサマリー\"\"\"\n    print(\"\\n\" + \"=\"*60)\n    print(\"🎯 Dogs vs Cats 分類タスク - 最終結果\")\n    print(\"=\"*60)\n    print(f\"📊 モデル性能:\")\n    print(f\"  - 最終検証精度: {best_val_acc:.2f}%\")\n    print(f\"  - 訓練エポック数: {num_epochs}\")\n    print(f\"  - 使用モデル: ResNet50\")\n    print(f\"  - 画像サイズ: {Config.IMG_SIZE}x{Config.IMG_SIZE}\")\n    print(f\"\\n🔧 実装した工夫:\")\n    print(f\"  - データ拡張 (水平反転、回転、色調変更)\")\n    print(f\"  - ImageNet事前学習済みモデル\")\n    print(f\"  - 学習率スケジューラー\")\n    print(f\"  - 早期停止\")\n    print(f\"  - ドロップアウト\")\n    print(f\"  - AdamWオプティマイザー\")\n    print(f\"  - 層化サンプリング\")\n    print(f\"\\n📁 出力ファイル:\")\n    print(f\"  - best_model.pth (最良モデル)\")\n    print(f\"  - submission.csv (Kaggle提出用)\")\n    print(f\"  - training_history.png (学習履歴)\")\n    print(f\"  - prediction_distribution.png (予測分布)\")\n    print(\"=\"*60)\n\n# ===== メイン実行関数 =====\ndef main():\n    \"\"\"メイン実行関数\"\"\"\n    print(\"🐕🐱 Dogs vs Cats Redux: Kernels Edition - 改良版\")\n    print(\"=\"*60)\n    \n    # 1. データセット準備\n    work_dir = extract_datasets()\n    train_dir, test_dir = show_data_info(work_dir)\n    \n    # 2. 設定表示\n    Config.print_config()\n    \n    # 3. データ変換設定\n    train_transform, val_transform = get_transforms()\n    \n    # 4. データローダー準備\n    train_loader, val_loader = prepare_data_loaders(train_dir, train_transform, val_transform)\n    \n    # 5. モデル準備\n    model, criterion, optimizer, scheduler = create_model()\n    \n    # 6. 学習実行\n    train_losses, val_losses, val_accuracies, best_val_acc = train_model(\n        model, train_loader, val_loader, criterion, optimizer, scheduler\n    )\n    \n    # 7. 学習結果可視化\n    visualize_training(train_losses, val_losses, val_accuracies)\n    \n    # 8. テストデータで予測\n    predictions = predict_test_data(model, test_dir, val_transform)\n    \n    # 9. 予測分布可視化\n    visualize_predictions(predictions)\n    \n    # 10. 最終サマリー\n    print_final_summary(best_val_acc, len(train_losses))\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-18T05:40:00.977444Z","iopub.execute_input":"2025-07-18T05:40:00.978438Z"}},"outputs":[],"execution_count":null}]}