{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":31193,"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-12-05T05:26:44.390550Z","iopub.execute_input":"2025-12-05T05:26:44.390739Z","iopub.status.idle":"2025-12-05T05:26:45.905796Z","shell.execute_reply.started":"2025-12-05T05:26:44.390721Z","shell.execute_reply":"2025-12-05T05:26:45.904932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision\nfrom torchvision import transforms, models\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\nimport zipfile\nfrom tqdm import tqdm\nimport random","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:42:46.167756Z","iopub.execute_input":"2025-12-05T05:42:46.168470Z","iopub.status.idle":"2025-12-05T05:42:52.466856Z","shell.execute_reply.started":"2025-12-05T05:42:46.168442Z","shell.execute_reply":"2025-12-05T05:42:52.466006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nTRAIN_ZIP = '/kaggle/input/dogs-vs-cats/train.zip'\nTEST_ZIP  = '/kaggle/input/dogs-vs-cats/test1.zip'\nWORK_DIR  = '/kaggle/working/dogs-vs-cats'\n\nos.makedirs(WORK_DIR, exist_ok=True)\n\ndef safe_unzip(zip_path, extract_to):\n    print('Unzipping', zip_path, '->', extract_to)\n    with zipfile.ZipFile(zip_path,'r') as z:\n        z.extractall(extract_to)\nif os.path.exists(TRAIN_ZIP):\n    safe_unzip(TRAIN_ZIP, WORK_DIR)\nelse:\n    print('Train zip not found at', TRAIN_ZIP)\nif os.path.exists(TEST_ZIP):\n    safe_unzip(TEST_ZIP, WORK_DIR)\nelse:\n    print('Test zip not found at', TEST_ZIP)\n\npossible_train_dirs = [\n    os.path.join(WORK_DIR, 'train'),\n    WORK_DIR\n]\ntrain_found = None\nfor d in possible_train_dirs:\n    if os.path.isdir(d) and any(p.suffix.lower() == '.jpg' for p in pathlib.Path(d).glob('*.jpg')):\n        train_found = d\n        break\n\nif train_found is None:\n    for root, dirs, files in os.walk(WORK_DIR):\n        if any(f.lower().endswith('.jpg') for f in files):\n            train_found = root\n            break\n\nif train_found is None:\n    raise FileNotFoundError('Could not find train images after unzipping. Looked in: ' + str(possible_train_dirs))\nprint('Train images found in:', train_found)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:42:00.421858Z","iopub.execute_input":"2025-12-05T05:42:00.422436Z","iopub.status.idle":"2025-12-05T05:42:08.819521Z","shell.execute_reply.started":"2025-12-05T05:42:00.422405Z","shell.execute_reply":"2025-12-05T05:42:08.818746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cek struktur folder\ntrain_dir = '/kaggle/working/dogs-vs-cats/train'\ntest_dir = '/kaggle/working/dogs-vs-cats/test'\n\ntrain_files = os.listdir(train_dir)\nprint(f\"Total training images: {len(train_files)}\")\n\n# Hitung jumlah gambar per kelas\ncats = [f for f in train_files if 'cat' in f]\ndogs = [f for f in train_files if 'dog' in f]\nprint(f\"Cats: {len(cats)}\")\nprint(f\"Dogs: {len(dogs)}\")\n\n# Tampilkan beberapa contoh nama file\nprint(\"\\nContoh nama file:\")\nprint(train_files[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:42:33.793434Z","iopub.execute_input":"2025-12-05T05:42:33.793728Z","iopub.status.idle":"2025-12-05T05:42:33.813917Z","shell.execute_reply.started":"2025-12-05T05:42:33.793706Z","shell.execute_reply":"2025-12-05T05:42:33.813173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualisasi beberapa gambar\ndef show_images(image_list, n=8):\n    fig, axes = plt.subplots(2, 4, figsize=(15, 8))\n    axes = axes.ravel()\n    \n    for i, img_name in enumerate(random.sample(image_list, n)):\n        img_path = os.path.join(train_dir, img_name)\n        img = Image.open(img_path)\n        axes[i].imshow(img)\n        axes[i].set_title(img_name.split('.')[0].upper())\n        axes[i].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\nshow_images(train_files)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:42:54.514593Z","iopub.execute_input":"2025-12-05T05:42:54.514821Z","iopub.status.idle":"2025-12-05T05:42:55.543935Z","shell.execute_reply.started":"2025-12-05T05:42:54.514803Z","shell.execute_reply":"2025-12-05T05:42:55.542914Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Prepare Dataset and DataLoader","metadata":{}},{"cell_type":"code","source":"# Custom Dataset Class\nclass DogsVsCatsDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.root_dir = root_dir\n        self.transform = transform\n        self.images = [f for f in os.listdir(root_dir) if f.endswith('.jpg')]\n        \n    def __len__(self):\n        return len(self.images)\n    \n    def __getitem__(self, idx):\n        img_name = self.images[idx]\n        img_path = os.path.join(self.root_dir, img_name)\n        image = Image.open(img_path).convert('RGB')\n        \n        # Label: 0 untuk cat, 1 untuk dog\n        label = 0 if 'cat' in img_name else 1\n        \n        if self.transform:\n            image = self.transform(image)\n        \n        return image, label\n\n# Data Augmentation dan Preprocessing\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\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\nval_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# Split data menjadi train dan validation (80:20)\nfrom sklearn.model_selection import train_test_split\n\nall_files = os.listdir(train_dir)\ntrain_files, val_files = train_test_split(all_files, test_size=0.2, random_state=42)\n\n# Create temporary directories for train and val\nos.makedirs('data/train', exist_ok=True)\nos.makedirs('data/val', exist_ok=True)\n\n# Copy files (atau bisa gunakan symlink untuk hemat space)\nimport shutil\n\nfor f in tqdm(train_files, desc='Copying train files'):\n    shutil.copy(os.path.join(train_dir, f), os.path.join('data/train', f))\n\nfor f in tqdm(val_files, desc='Copying val files'):\n    shutil.copy(os.path.join(train_dir, f), os.path.join('data/val', f))\n\nprint(f\"Train samples: {len(train_files)}\")\nprint(f\"Validation samples: {len(val_files)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:44:00.299683Z","iopub.execute_input":"2025-12-05T05:44:00.300560Z","iopub.status.idle":"2025-12-05T05:44:02.861442Z","shell.execute_reply.started":"2025-12-05T05:44:00.300526Z","shell.execute_reply":"2025-12-05T05:44:02.860664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create datasets and dataloaders\nbatch_size = 32\n\ntrain_dataset = DogsVsCatsDataset('data/train', transform=train_transform)\nval_dataset = DogsVsCatsDataset('data/val', transform=val_transform)\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(f\"Train batches: {len(train_loader)}\")\nprint(f\"Validation batches: {len(val_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:44:09.235846Z","iopub.execute_input":"2025-12-05T05:44:09.236586Z","iopub.status.idle":"2025-12-05T05:44:09.259864Z","shell.execute_reply.started":"2025-12-05T05:44:09.236558Z","shell.execute_reply":"2025-12-05T05:44:09.259107Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Creation","metadata":{}},{"cell_type":"code","source":"# Menggunakan Pre-trained ResNet18 dengan Transfer Learning\nclass DogCatClassifier(nn.Module):\n    def __init__(self, num_classes=2):\n        super(DogCatClassifier, self).__init__()\n        self.model = models.resnet18(weights=True)\n        \n        # Freeze early layers\n        for param in list(self.model.parameters())[:-10]:\n            param.requires_grad = False\n        \n        # Replace final layer\n        num_features = self.model.fc.in_features\n        self.model.fc = nn.Sequential(\n            nn.Dropout(0.5),\n            nn.Linear(num_features, num_classes)\n        )\n    \n    def forward(self, x):\n        return self.model(x)\n\n# Initialize model\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = DogCatClassifier(num_classes=2).to(device)\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:45:31.818532Z","iopub.execute_input":"2025-12-05T05:45:31.819191Z","iopub.status.idle":"2025-12-05T05:45:32.096881Z","shell.execute_reply.started":"2025-12-05T05:45:31.819167Z","shell.execute_reply":"2025-12-05T05:45:32.096101Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"# Loss function dan optimizer\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.001)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', patience=3, factor=0.5)\n\n# Training parameters\nnum_epochs = 15\nbest_val_acc = 0.0\n\ndef train_epoch(model, loader, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n    \n    pbar = tqdm(loader, desc='Training')\n    for images, labels in pbar:\n        images, labels = images.to(device), labels.to(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 = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n        \n        pbar.set_postfix({'loss': running_loss/len(loader), 'acc': 100.*correct/total})\n    \n    epoch_loss = running_loss / len(loader)\n    epoch_acc = 100. * correct / total\n    return epoch_loss, epoch_acc\n\ndef validate(model, loader, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        pbar = tqdm(loader, desc='Validation')\n        for images, labels in pbar:\n            images, labels = images.to(device), labels.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            \n            running_loss += loss.item()\n            _, predicted = outputs.max(1)\n            total += labels.size(0)\n            correct += predicted.eq(labels).sum().item()\n            \n            pbar.set_postfix({'loss': running_loss/len(loader), 'acc': 100.*correct/total})\n    \n    epoch_loss = running_loss / len(loader)\n    epoch_acc = 100. * correct / total\n    return epoch_loss, epoch_acc\n\n\n# Training history\nhistory = {\n    'train_loss': [],\n    'train_acc': [],\n    'val_loss': [],\n    'val_acc': []\n}\n\n# Training loop\nfor epoch in range(num_epochs):\n    print(f'\\nEpoch {epoch+1}/{num_epochs}')\n    print('-' * 50)\n    \n    # Train\n    train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)\n    history['train_loss'].append(train_loss)\n    history['train_acc'].append(train_acc)\n    \n    # Validate\n    val_loss, val_acc = validate(model, val_loader, criterion, device)\n    history['val_loss'].append(val_loss)\n    history['val_acc'].append(val_acc)\n    \n    # Learning rate scheduler\n    scheduler.step(val_loss)\n    \n    print(f'\\nTrain Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')\n    print(f'Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%')\n    \n    # Save best model\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save({\n            'epoch': epoch,\n            'model_state_dict': model.state_dict(),\n            'optimizer_state_dict': optimizer.state_dict(),\n            'val_acc': val_acc,\n            'val_loss': val_loss,\n        }, 'best_model.pth')\n        print(f'✓ Saved best model with accuracy: {val_acc:.2f}%')\n\nprint('\\n' + '='*50)\nprint(f'Training completed! Best validation accuracy: {best_val_acc:.2f}%')\nprint('='*50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-05T05:46:20.802668Z","iopub.execute_input":"2025-12-05T05:46:20.803369Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Visualisasi Hasil Training","metadata":{}},{"cell_type":"code","source":"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n\n# Loss\nax1.plot(history['train_loss'], label='Train Loss', marker='o')\nax1.plot(history['val_loss'], label='Val Loss', marker='o')\nax1.set_xlabel('Epoch')\nax1.set_ylabel('Loss')\nax1.set_title('Training and Validation Loss')\nax1.legend()\nax1.grid(True)\n\n# Accuracy\nax2.plot(history['train_acc'], label='Train Accuracy', marker='o')\nax2.plot(history['val_acc'], label='Val Accuracy', marker='o')\nax2.set_xlabel('Epoch')\nax2.set_ylabel('Accuracy (%)')\nax2.set_title('Training and Validation Accuracy')\nax2.legend()\nax2.grid(True)\n\nplt.tight_layout()\nplt.savefig('training_history.png', dpi=300, bbox_inches='tight')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluasi model","metadata":{}},{"cell_type":"code","source":"# Load best model\ncheckpoint = torch.load('best_model.pth')\nmodel.load_state_dict(checkpoint['model_state_dict'])\nprint(f\"Loaded best model from epoch {checkpoint['epoch']+1}\")\nprint(f\"Best validation accuracy: {checkpoint['val_acc']:.2f}%\")\n\n# Confusion Matrix\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\n\ndef get_predictions(model, loader, device):\n    model.eval()\n    all_preds = []\n    all_labels = []\n    \n    with torch.no_grad():\n        for images, labels in tqdm(loader, desc='Getting predictions'):\n            images = images.to(device)\n            outputs = model(images)\n            _, predicted = outputs.max(1)\n            \n            all_preds.extend(predicted.cpu().numpy())\n            all_labels.extend(labels.numpy())\n    \n    return np.array(all_preds), np.array(all_labels)\n\n# Get predictions\npreds, labels = get_predictions(model, val_loader, device)\n\n# Confusion matrix\ncm = confusion_matrix(labels, preds)\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Cat', 'Dog'], yticklabels=['Cat', 'Dog'])\nplt.xlabel('Predicted')\nplt.ylabel('Actual')\nplt.title('Confusion Matrix')\nplt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')\nplt.show()\n\n# Classification report\nprint('\\nClassification Report:')\nprint(classification_report(labels, preds, target_names=['Cat', 'Dog']))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize predictions\ndef visualize_predictions(model, dataset, device, n=8):\n    model.eval()\n    indices = random.sample(range(len(dataset)), n)\n    \n    fig, axes = plt.subplots(2, 4, figsize=(15, 8))\n    axes = axes.ravel()\n    \n    class_names = ['Cat', 'Dog']\n    \n    with torch.no_grad():\n        for i, idx in enumerate(indices):\n            image, label = dataset[idx]\n            image_tensor = image.unsqueeze(0).to(device)\n            \n            output = model(image_tensor)\n            prob = torch.softmax(output, dim=1)\n            pred_class = output.argmax(1).item()\n            confidence = prob[0][pred_class].item() * 100\n            \n            # Denormalize image for display\n            img = image.cpu().numpy().transpose(1, 2, 0)\n            mean = np.array([0.485, 0.456, 0.406])\n            std = np.array([0.229, 0.224, 0.225])\n            img = std * img + mean\n            img = np.clip(img, 0, 1)\n            \n            axes[i].imshow(img)\n            color = 'green' if pred_class == label else 'red'\n            axes[i].set_title(f'True: {class_names[label]}\\nPred: {class_names[pred_class]} ({confidence:.1f}%)', \n                            color=color, fontweight='bold')\n            axes[i].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('predictions_sample.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\nvisualize_predictions(model, val_dataset, device)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Testing pada test set","metadata":{}}]}