{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nimport seaborn as sn\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torchvision import transforms, models\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.metrics import confusion_matrix\nimport sys","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:24.809899Z","iopub.execute_input":"2026-06-20T13:40:24.810103Z","iopub.status.idle":"2026-06-20T13:40:38.817949Z","shell.execute_reply.started":"2026-06-20T13:40:24.810080Z","shell.execute_reply":"2026-06-20T13:40:38.817355Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImageDataset(Dataset):\n    def __init__(self, fake_paths, real_paths, transform=None):\n        self.image_paths = fake_paths + real_paths\n        self.labels = [0] * len(fake_paths) + [1] * len(real_paths)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        label = self.labels[idx]\n        if self.transform:\n            image = self.transform(image)\n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.819793Z","iopub.execute_input":"2026-06-20T13:40:38.820204Z","iopub.status.idle":"2026-06-20T13:40:38.825903Z","shell.execute_reply.started":"2026-06-20T13:40:38.820179Z","shell.execute_reply":"2026-06-20T13:40:38.825089Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IM_SIZE = 112\nMEAN = [0.485, 0.456, 0.406]\nSTD  = [0.229, 0.224, 0.225]\n \n\ntrain_transforms = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((128, 128)),\n    transforms.RandomCrop((IM_SIZE, IM_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(MEAN, STD),\n])\n \ntest_transforms = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((IM_SIZE, IM_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(MEAN, STD),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.826770Z","iopub.execute_input":"2026-06-20T13:40:38.827044Z","iopub.status.idle":"2026-06-20T13:40:38.844477Z","shell.execute_reply.started":"2026-06-20T13:40:38.827010Z","shell.execute_reply":"2026-06-20T13:40:38.843658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImageModel(nn.Module):\n    \"\"\"\n    ResNeXt50 backbone → global avg pool → dropout → linear classifier.\n    No LSTM, no sequence dimension.\n    \"\"\"\n    def __init__(self, num_classes=2, latent_dim=2048, dropout=0.4):\n        super(ImageModel, self).__init__()\n    \n        weights = models.ResNeXt50_32X4D_Weights.DEFAULT\n        backbone = models.resnext50_32x4d(weights=weights)\n        # Remove the final FC and avg-pool layers\n        self.features = nn.Sequential(*list(backbone.children())[:-2])\n        self.avgpool  = nn.AdaptiveAvgPool2d(1)\n        self.dropout  = nn.Dropout(dropout)\n        self.classifier = nn.Linear(latent_dim, num_classes)\n \n    def forward(self, x):\n        # x: (batch, C, H, W)\n        fmap = self.features(x)          # (batch, 2048, h', w')\n        x    = self.avgpool(fmap)        # (batch, 2048, 1, 1)\n        x    = x.view(x.size(0), -1)    # (batch, 2048)\n        out  = self.classifier(self.dropout(x))  # (batch, num_classes)\n        return fmap, out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.845268Z","iopub.execute_input":"2026-06-20T13:40:38.845558Z","iopub.status.idle":"2026-06-20T13:40:38.856478Z","shell.execute_reply.started":"2026-06-20T13:40:38.845526Z","shell.execute_reply":"2026-06-20T13:40:38.855876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AverageMeter:\n    def __init__(self):\n        self.reset()\n \n    def reset(self):\n        self.val = self.avg = self.sum = self.count = 0\n \n    def update(self, val, n=1):\n        self.val   = val\n        self.sum  += val * n\n        self.count += n\n        self.avg   = self.sum / self.count\n \n \ndef calculate_accuracy(outputs, targets):\n    batch_size = targets.size(0)\n    _, pred = outputs.topk(1, 1, True)\n    pred    = pred.t()\n    correct = pred.eq(targets.view(1, -1))\n    return 100.0 * correct.float().sum().item() / batch_size","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.857346Z","iopub.execute_input":"2026-06-20T13:40:38.857873Z","iopub.status.idle":"2026-06-20T13:40:38.872979Z","shell.execute_reply.started":"2026-06-20T13:40:38.857842Z","shell.execute_reply":"2026-06-20T13:40:38.872117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----- 6. Training loop -----\ndef train_epoch(epoch, num_epochs, loader, model, criterion, optimizer):\n    model.train()\n    losses     = AverageMeter()\n    accuracies = AverageMeter()\n \n    for i, (inputs, targets) in enumerate(loader):\n        if torch.cuda.is_available():\n            inputs  = inputs.cuda()\n            targets = targets.cuda().long()\n \n        _, outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        acc  = calculate_accuracy(outputs, targets)\n \n        losses.update(loss.item(), inputs.size(0))\n        accuracies.update(acc, inputs.size(0))\n \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n \n        sys.stdout.write(\n            \"\\r[Epoch %d/%d] [Batch %d/%d] [Loss: %.4f, Acc: %.2f%%]\"\n            % (epoch, num_epochs, i, len(loader), losses.avg, accuracies.avg)\n        )\n \n   \n    return losses.avg, accuracies.avg\n ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.873882Z","iopub.execute_input":"2026-06-20T13:40:38.874148Z","iopub.status.idle":"2026-06-20T13:40:38.885606Z","shell.execute_reply.started":"2026-06-20T13:40:38.874118Z","shell.execute_reply":"2026-06-20T13:40:38.884960Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----- 7. Evaluation loop -----\ndef evaluate(epoch, model, loader, criterion):\n    print('\\nEvaluating...')\n    model.eval()\n    losses     = AverageMeter()\n    accuracies = AverageMeter()\n    all_preds, all_true = [], []\n \n    with torch.no_grad():\n        for i, (inputs, targets) in enumerate(loader):\n            if torch.cuda.is_available():\n                inputs  = inputs.cuda()\n                targets = targets.cuda().long()\n \n            _, outputs = model(inputs)\n            loss = criterion(outputs, targets)\n            acc  = calculate_accuracy(outputs, targets)\n \n            _, preds = torch.max(outputs, 1)\n            all_true  += targets.cpu().numpy().tolist()\n            all_preds += preds.cpu().numpy().tolist()\n \n            losses.update(loss.item(), inputs.size(0))\n            accuracies.update(acc, inputs.size(0))\n \n            sys.stdout.write(\n                \"\\r[Batch %d/%d] [Loss: %.4f, Acc: %.2f%%]\"\n                % (i, len(loader), losses.avg, accuracies.avg)\n            )\n \n    print(f'\\nVal Accuracy: {accuracies.avg:.2f}%')\n    return all_true, all_preds, losses.avg, accuracies.avg\n ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.887764Z","iopub.execute_input":"2026-06-20T13:40:38.888080Z","iopub.status.idle":"2026-06-20T13:40:38.900812Z","shell.execute_reply.started":"2026-06-20T13:40:38.888060Z","shell.execute_reply":"2026-06-20T13:40:38.900134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_loss(train_losses, val_losses):\n    epochs = range(1, len(train_losses) + 1)\n    plt.figure()\n    plt.plot(epochs, train_losses, 'g', label='Train loss')\n    plt.plot(epochs, val_losses,   'b', label='Val loss')\n    plt.title('Training vs Validation Loss')\n    plt.xlabel('Epochs'); plt.ylabel('Loss')\n    plt.legend(); plt.show()\n \n \ndef plot_accuracy(train_accs, val_accs):\n    epochs = range(1, len(train_accs) + 1)\n    plt.figure()\n    plt.plot(epochs, train_accs, 'g', label='Train accuracy')\n    plt.plot(epochs, val_accs,   'b', label='Val accuracy')\n    plt.title('Training vs Validation Accuracy')\n    plt.xlabel('Epochs'); plt.ylabel('Accuracy (%)')\n    plt.legend(); plt.show()\n \n \ndef print_confusion_matrix(y_true, y_pred):\n    cm = confusion_matrix(y_true, y_pred, labels=[0, 1])\n\n    print(f'Actual Fake predicted Fake = {cm[0][0]}')\n    print(f'Actual Fake predicted Real = {cm[0][1]}')\n    print(f'Actual Real predicted Fake = {cm[1][0]}')\n    print(f'Actual Real predicted Real = {cm[1][1]}')\n\n    df_cm = pd.DataFrame(cm, index=['Fake', 'Real'], columns=['Fake', 'Real'])\n    sn.set(font_scale=1.4)\n    sn.heatmap(df_cm, annot=True, fmt='d', annot_kws={\"size\": 16})\n    plt.ylabel('Actual', size=20)\n    plt.xlabel('Predicted', size=20)\n\n    acc = (cm[0][0] + cm[1][1]) / cm.sum()\n    print(f'Calculated Accuracy: {acc * 100:.2f}%')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.901709Z","iopub.execute_input":"2026-06-20T13:40:38.902093Z","iopub.status.idle":"2026-06-20T13:40:38.914044Z","shell.execute_reply.started":"2026-06-20T13:40:38.902072Z","shell.execute_reply":"2026-06-20T13:40:38.913138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# ----- 9. Main — wire it all together -----\n# ============================================================\nif __name__ == '__main__':\n    import glob\n    import random\n    BASE = '/kaggle/input/datasets/tristanzhang32/ai-generated-images-vs-real-images'\n    # -- Load image paths using official splits\n    train_fake = glob.glob(f'{BASE}/train/fake/*.jpg')\n    train_real = glob.glob(f'{BASE}/train/real/*.jpg')\n    val_fake   = glob.glob(f'{BASE}/valid/fake/*.jpg')\n    val_real   = glob.glob(f'{BASE}/valid/real/*.jpg')\n    print(f'Train — Fake: {len(train_fake)}  Real: {len(train_real)}')\n    print(f'Val   — Fake: {len(val_fake)}    Real: {len(val_real)}')\n    # -- Datasets & loaders\n    train_dataset = ImageDataset(train_fake, train_real, transform=train_transforms)\n    val_dataset   = ImageDataset(val_fake,   val_real,   transform=test_transforms)\n    train_loader  = DataLoader(train_dataset, batch_size=32, shuffle=True,  num_workers=4)\n    val_loader    = DataLoader(val_dataset,   batch_size=32, shuffle=False, num_workers=4)\n    # -- Model, loss, optimiser\n    model     = ImageModel(num_classes=2).cuda()\n    criterion = nn.CrossEntropyLoss().cuda()\n    optimizer = torch.optim.Adam(model.parameters(), lr=1e-5, weight_decay=1e-5)\n    # -- Training\n    NUM_EPOCHS = 20\n    train_losses, val_losses         = [], []\n    train_accuracies, val_accuracies = [], []\n    best_val_acc = 0.0\n    best_epoch = 0\n    for epoch in range(1, NUM_EPOCHS + 1):\n        tl, ta = train_epoch(epoch, NUM_EPOCHS, train_loader, model, criterion, optimizer)\n        vt, vp, vl, va = evaluate(epoch, model, val_loader, criterion)\n        if va > best_val_acc:\n            best_val_acc = va\n            best_epoch = epoch\n            torch.save(model.state_dict(), '/kaggle/working/best_checkpoint_image.pt')\n            print(f'\\nSaved best model at epoch {epoch} with val accuracy {va:.2f}%')\n        train_losses.append(tl);     val_losses.append(vl)\n        train_accuracies.append(ta); val_accuracies.append(va)\n    # -- Plots & confusion matrix (from last eval epoch)\n    plot_loss(train_losses, val_losses)\n    plot_accuracy(train_accuracies, val_accuracies)\n    print_confusion_matrix(vt, vp)\n    print(f'Best validation accuracy: {best_val_acc:.2f}% at epoch {best_epoch}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:40:38.915014Z","iopub.execute_input":"2026-06-20T13:40:38.915316Z","iopub.status.idle":"2026-06-20T13:41:45.321654Z","shell.execute_reply.started":"2026-06-20T13:40:38.915265Z","shell.execute_reply":"2026-06-20T13:41:45.320493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# -- Save final model\nsave_path = \"/kaggle/working/deepfake_final.pt\"\ntorch.save(model.state_dict(), save_path)\nprint(f\"✅ Model saved → {save_path}\")\n\n# -- Verify\nsize_mb = os.path.getsize(save_path) / (1024*1024)\nprint(f\"File size: {size_mb:.2f} MB\")\nprint(\"Files in output:\", os.listdir('/kaggle/working/'))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T13:41:45.322452Z","iopub.status.idle":"2026-06-20T13:41:45.322724Z","shell.execute_reply.started":"2026-06-20T13:41:45.322604Z","shell.execute_reply":"2026-06-20T13:41:45.322619Z"}},"outputs":[],"execution_count":null}]}