{"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":"nvidiaTeslaT4","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-14T08:03:15.160662Z","iopub.execute_input":"2025-07-14T08:03:15.160956Z","iopub.status.idle":"2025-07-14T08:03:15.490847Z","shell.execute_reply.started":"2025-07-14T08:03:15.160928Z","shell.execute_reply":"2025-07-14T08:03:15.489949Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"ファイルの解凍","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\n\nos.makedirs('/kaggle/working/train', exist_ok=True)\nos.makedirs('/kaggle/working/test', exist_ok=True)\n\ntrain_zip_path = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip'\ntrain_extract_path = '/kaggle/working/train'\n\nwith zipfile.ZipFile(train_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(train_extract_path)\n\ntest_zip_path = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip'\ntest_extract_path = '/kaggle/working/test'\n\nwith zipfile.ZipFile(test_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(test_extract_path)\n\nprint(\"ファイルの解凍が完了しました。\")\nprint(\"学習用画像（一部）:\", os.listdir('/kaggle/working/train/train')[:5])\nprint(\"テスト用画像（一部）:\", os.listdir('/kaggle/working/test/test')[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T08:03:00.969521Z","iopub.execute_input":"2025-07-14T08:03:00.969735Z","iopub.status.idle":"2025-07-14T08:03:15.159671Z","shell.execute_reply.started":"2025-07-14T08:03:00.969718Z","shell.execute_reply":"2025-07-14T08:03:15.158851Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"ライブラリのインストール","metadata":{}},{"cell_type":"code","source":"!pip install ultralytics timm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T08:05:21.621738Z","iopub.execute_input":"2025-07-14T08:05:21.622386Z","iopub.status.idle":"2025-07-14T08:06:37.867534Z","shell.execute_reply.started":"2025-07-14T08:05:21.622355Z","shell.execute_reply":"2025-07-14T08:06:37.866430Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"YOLOによる犬猫の検出とトリミング","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nfrom ultralytics import YOLO\nfrom PIL import Image\nfrom tqdm.notebook import tqdm\n\nmodel = YOLO('yolov8n.pt')\n\ncoco_names = model.names\ntarget_class_ids = [k for k, v in coco_names.items() if v in ['dog', 'cat']]\n\ndef crop_objects(input_dir, output_dir, image_limit=None):\n    os.makedirs(output_dir, exist_ok=True)\n    \n    filenames = os.listdir(input_dir)\n    if image_limit:\n        filenames = filenames[:image_limit]\n    \n    cropped_count = 0\n    for filename in tqdm(filenames, desc=f\"Processing {os.path.basename(input_dir)}\"):\n        filepath = os.path.join(input_dir, filename)\n        \n        try:\n            results = model(filepath, verbose=False)\n        except Exception as e:\n            print(f\"Error processing {filename}: {e}\")\n            continue\n\n        img = Image.open(filepath)\n        \n        for i, res in enumerate(results):\n            boxes = res.boxes\n            for box in boxes:\n                class_id = int(box.cls[0])\n                \n                if class_id in target_class_ids:\n                    xyxy = box.xyxy[0].cpu().numpy()\n                    \n                    cropped_img = img.crop(xyxy)\n                    \n                    new_filename = f\"{os.path.splitext(filename)[0]}_crop{i}{os.path.splitext(filename)[1]}\"\n                    save_path = os.path.join(output_dir, new_filename)\n                    \n                    cropped_img.save(save_path)\n                    cropped_count += 1\n\n    print(f\"Finished processing {input_dir}. Created {cropped_count} cropped images in {output_dir}\")\n\ntrain_input_dir = '/kaggle/working/train/train'\ntrain_output_dir = '/kaggle/working/cropped_train'\ncrop_objects(train_input_dir, train_output_dir)\n\ntest_input_dir = '/kaggle/working/test/test'\ntest_output_dir = '/kaggle/working/cropped_test'\ncrop_objects(test_input_dir, test_output_dir)\n\nprint(\"\\n--- Cropped Train Images (sample) ---\")\nprint(os.listdir(train_output_dir)[:5])\nprint(\"\\n--- Cropped Test Images (sample) ---\")\nprint(os.listdir(test_output_dir)[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T08:08:27.544210Z","iopub.execute_input":"2025-07-14T08:08:27.544559Z","iopub.status.idle":"2025-07-14T08:17:13.075893Z","shell.execute_reply.started":"2025-07-14T08:08:27.544527Z","shell.execute_reply":"2025-07-14T08:17:13.075254Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"学習と評価","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport timm\nimport torch.nn as nn\nimport torch.optim as optim\nfrom tqdm.notebook import tqdm\n\ntrain_dir = '/kaggle/working/cropped_train'\n\nfilepaths = [os.path.join(train_dir, f) for f in os.listdir(train_dir)]\ndf = pd.DataFrame({'filepath': filepaths})\ndf['label'] = df['filepath'].apply(lambda x: 1 if 'dog' in os.path.basename(x) else 0)\n\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['label'])\n\nIMG_SIZE = 224\ndata_transforms = {\n    'train': transforms.Compose([\n        transforms.Resize((IMG_SIZE, IMG_SIZE)),\n        transforms.RandomHorizontalFlip(),\n        transforms.RandomRotation(10),\n        transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n    'val': transforms.Compose([\n        transforms.Resize((IMG_SIZE, IMG_SIZE)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n}\n\nclass CroppedDataset(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        image_path = self.df.iloc[idx]['filepath']\n        label = self.df.iloc[idx]['label']\n        try:\n            image = Image.open(image_path).convert('RGB')\n            if self.transform:\n                image = self.transform(image)\n            return image, torch.tensor(label, dtype=torch.long)\n        except (IOError, SyntaxError) as e:\n            print(f\"Skipping corrupted image: {image_path}\")\n            return self.__getitem__((idx + 1) % len(self))\n\n\nBATCH_SIZE = 32\ntrain_dataset = CroppedDataset(train_df, transform=data_transforms['train'])\nval_dataset = CroppedDataset(val_df, transform=data_transforms['val'])\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\nprint(\"データ準備完了\")\nprint(f\"学習データ数: {len(train_dataset)}, 検証データ数: {len(val_dataset)}\")\n\n\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = timm.create_model('convnext_small', pretrained=True, num_classes=2)\nmodel.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.AdamW(model.parameters(), lr=1e-5) # ファインチューニングのため学習率は小さめに設定\n\nprint(f\"モデル準備完了。デバイス: {device}\")\n\n\n\nNUM_EPOCHS = 5 # エポック数（必要に応じて調整）\n\nfor epoch in range(NUM_EPOCHS):\n    model.train()\n    running_loss = 0.0\n    progress_bar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{NUM_EPOCHS} [Training]\")\n    for inputs, labels in progress_bar:\n        inputs, labels = inputs.to(device), 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        running_loss += loss.item() * inputs.size(0)\n        progress_bar.set_postfix(loss=loss.item())\n    \n    epoch_train_loss = running_loss / len(train_dataset)\n\n    model.eval()\n    val_loss = 0.0\n    corrects = 0\n    with torch.no_grad():\n        progress_bar_val = tqdm(val_loader, desc=f\"Epoch {epoch+1}/{NUM_EPOCHS} [Validation]\")\n        for inputs, labels in progress_bar_val:\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            _, preds = torch.max(outputs, 1)\n            \n            val_loss += loss.item() * inputs.size(0)\n            corrects += torch.sum(preds == labels.data)\n\n    epoch_val_loss = val_loss / len(val_dataset)\n    epoch_val_acc = corrects.double() / len(val_dataset)\n\n    print(f\"Epoch {epoch+1}/{NUM_EPOCHS} -> \"\n          f\"Train Loss: {epoch_train_loss:.4f} | \"\n          f\"Val Loss: {epoch_val_loss:.4f} | \"\n          f\"Val Acc: {epoch_val_acc:.4f}\")\n\nprint(\"\\n学習が完了しました。\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T08:25:41.475214Z","iopub.execute_input":"2025-07-14T08:25:41.476336Z","iopub.status.idle":"2025-07-14T09:26:20.957318Z","shell.execute_reply.started":"2025-07-14T08:25:41.476309Z","shell.execute_reply":"2025-07-14T09:26:20.956559Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"推論","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom tqdm.notebook import tqdm\nimport torch.nn.functional as F\nimport re\n\ntest_dir = '/kaggle/working/cropped_test'\n\nclass TestDataset(Dataset):\n    def __init__(self, filepaths, transform=None):\n        self.filepaths = filepaths\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.filepaths)\n\n    def __getitem__(self, idx):\n        filepath = self.filepaths[idx]\n        original_id = re.search(r'(\\d+)_crop', os.path.basename(filepath))\n        if original_id:\n            image_id = int(original_id.group(1))\n        else: # もしcropがない場合（例：123.jpg）\n            image_id = int(os.path.splitext(os.path.basename(filepath))[0])\n            \n        try:\n            image = Image.open(filepath).convert('RGB')\n            if self.transform:\n                image = self.transform(image)\n            return image, image_id\n        except (IOError, SyntaxError) as e:\n            print(f\"Skipping corrupted image: {filepath}\")\n            return self.__getitem__((idx + 1) % len(self))\n\ntest_filepaths = [os.path.join(test_dir, f) for f in os.listdir(test_dir)]\n\ntest_dataset = TestDataset(test_filepaths, transform=data_transforms['val'])\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\nprint(\"テストデータの準備完了\")\n\nmodel.eval()  # モデルを評価モードに設定\nresults = []\n\nwith torch.no_grad():\n    progress_bar = tqdm(test_loader, desc=\"Inference\")\n    for inputs, image_ids in progress_bar:\n        inputs = inputs.to(device)\n        outputs = model(inputs)\n        \n        probabilities = F.softmax(outputs, dim=1)[:, 1].cpu().numpy()\n        \n        for i, image_id in enumerate(image_ids):\n            results.append({'id': image_id.item(), 'label': probabilities[i]})\n\nprint(\"推論完了\")\n\ninference_df = pd.DataFrame(results)\nsubmission_df = inference_df.groupby('id')['label'].mean().reset_index()\n\nsample_submission_df = pd.read_csv('/kaggle/input/dogs-vs-cats-redux-kernels-edition/sample_submission.csv')\nsubmission_df = sample_submission_df[['id']].merge(submission_df, on='id', how='left')\n\nsubmission_df['label'] = submission_df['label'].fillna(0.5)\n\nsubmission_df['label'] = submission_df['label'].clip(0.005, 0.995)\n\nprint(\"結果の集計完了\")\n\n\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"\\nsubmission.csvが作成されました。\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-14T09:28:53.526683Z","iopub.execute_input":"2025-07-14T09:28:53.527576Z","iopub.status.idle":"2025-07-14T09:30:29.288001Z","shell.execute_reply.started":"2025-07-14T09:28:53.527528Z","shell.execute_reply":"2025-07-14T09:30:29.287141Z"}},"outputs":[],"execution_count":null}]}