{"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":"gpu","dataSources":[{"sourceId":19991,"databundleVersionId":1117522,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Project Final","metadata":{}},{"cell_type":"markdown","source":"# Nguồn tham khảo\nhttps://www.kaggle.com/code/tuananh2011/alaska2/notebook ","metadata":{}},{"cell_type":"markdown","source":"# Khai báo các thư việc sử dụng ","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd\nimport torch\nimport os\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom torchvision.models.efficientnet import efficientnet_b2, EfficientNet_B2_Weights\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.metrics import accuracy_score, roc_curve","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.381016Z","iopub.execute_input":"2025-05-15T16:02:52.381358Z","iopub.status.idle":"2025-05-15T16:02:52.386959Z","shell.execute_reply.started":"2025-05-15T16:02:52.381323Z","shell.execute_reply":"2025-05-15T16:02:52.386179Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Thực hiện tiền xử lý ảnh","metadata":{}},{"cell_type":"code","source":"def preprocess_image(image_path, size=(512, 512)):\n    # Tải hình ảnh lên \n    img = Image.open(image_path)\n    \n    # Đổi sang màu YCbCr\n    img = img.convert(\"YCbCr\")\n    \n    # Đổi về mảng numpy\n    img_array = np.array(img, dtype=np.float64)\n    \n    # Trích xuất DCT residual\n    y_channel = img_array[:, :, 0]\n    high_freq = y_channel - cv2.GaussianBlur(y_channel, (3, 3), 0)\n    img_array[:, :, 0] = high_freq\n    \n    # Nghịch đảo từ kênh (H, W, C) về (C, H, W) để phù hợp với PyTorch\n    img_array = np.transpose(img_array, (2, 0, 1))\n    \n    # Chuẩn hóa pixel và dạng [0, 1]\n    img_array = img_array / 255.0\n    \n    # Đổi thành PyTorch tensor\n    img_tensor = torch.tensor(img_array, dtype=torch.float32)\n    \n    return img_tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.417847Z","iopub.execute_input":"2025-05-15T16:02:52.418225Z","iopub.status.idle":"2025-05-15T16:02:52.433119Z","shell.execute_reply.started":"2025-05-15T16:02:52.418193Z","shell.execute_reply":"2025-05-15T16:02:52.432493Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Tạo Dataset và Model","metadata":{}},{"cell_type":"code","source":"class SteganalysisDataset(Dataset):\n    def __init__(self, image_paths, labels, augment=False):\n        self.image_paths = image_paths\n        self.labels = labels\n        self.augment = augment\n        \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, index):\n        # Get image path and label\n        img_path = self.image_paths[index]\n        label = self.labels[index]\n        \n        # Xử lý ảnh\n        image = preprocess_image(img_path)\n        \n        # Lấy các tham số \n        if self.augment:\n            # Flip horizontally with 50% probability (safe for steganalysis)\n            if np.random.random() > 0.5:\n                image = torch.flip(image, [2])\n                \n            # Random crop and resize back (safe for steganalysis as it preserves patterns)\n            if np.random.random() > 0.5:\n                i, j = torch.randint(0, 32, (2,))\n                image = image[:, i:512-32+i, j:512-32+j]\n                image = F.interpolate(image.unsqueeze(0), size=(512, 512), \n                                     mode='bilinear', align_corners=False).squeeze(0)\n                \n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.434277Z","iopub.execute_input":"2025-05-15T16:02:52.434597Z","iopub.status.idle":"2025-05-15T16:02:52.447212Z","shell.execute_reply.started":"2025-05-15T16:02:52.434581Z","shell.execute_reply":"2025-05-15T16:02:52.446494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DCTLayer(nn.Module):\n    def __init__(self, block_size=8):\n        super(DCTLayer, self).__init__()\n        self.block_size = block_size\n        self.dct_matrix = self.create_dct_matrix(block_size)\n        self.dct_matrix_t = self.dct_matrix.t()\n\n        # Các vị trí tần số low, mid, high\n        self.freq_indices = {\n            'low': [(0, 0)],\n            'mid': [(2, 2), (1, 3), (3, 1)],\n            'high': [(6, 6), (5, 7), (7, 5)]\n        }\n\n    def create_dct_matrix(self, N):\n        dct = torch.zeros(N, N)\n        for k in range(N):\n            for n in range(N):\n                coeff = (1 / N)**0.5 if k == 0 else (2 / N)**0.5\n                dct[k, n] = coeff * torch.cos(torch.tensor(torch.pi * (2 * n + 1) * k / (2 * N)))\n        return dct\n\n    def forward(self, x):\n        B, C, H, W = x.shape\n        bs = self.block_size\n\n        # Padding để chia block chính xác\n        H_pad = (bs - H % bs) % bs\n        W_pad = (bs - W % bs) % bs\n        x = F.pad(x, (0, W_pad, 0, H_pad), mode='reflect')\n        _, _, H_p, W_p = x.shape\n\n        dct = self.dct_matrix.to(x.device)\n        dct_t = self.dct_matrix_t.to(x.device)\n\n        # Lấy block 8x8\n        x_blocks = x.unfold(2, bs, bs).unfold(3, bs, bs)  \n        x_blocks = x_blocks.contiguous().view(B, C, -1, bs, bs)\n\n        # Tính DCT\n        x_dct = torch.matmul(dct[None, None, :, :], x_blocks)\n        x_dct = torch.matmul(x_dct, dct_t[None, None, :, :])\n\n        # Lấy đặc trưng từ tần số low, mid, high\n        feature_maps = []\n        for f_type in ['low', 'mid', 'high']:\n            indices = self.freq_indices[f_type]\n            combined = torch.stack([x_dct[..., i, j] for (i, j) in indices], dim=-1)\n            combined = combined.mean(dim=-1)  # trung bình các coeff\n            feature_maps.append(combined)\n\n        features = torch.cat(feature_maps, dim=1)  # (B, 9, N_blocks)\n\n        # reshape về H,W\n        H_blocks = H_p // bs\n        W_blocks = W_p // bs\n        features = features.view(B, 9, H_blocks, W_blocks)\n        features = F.interpolate(features, size=(H_p, W_p), mode='bilinear', align_corners=False)\n\n        return features[:, :, :H, :W]  # bỏ padding","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.447962Z","iopub.execute_input":"2025-05-15T16:02:52.448139Z","iopub.status.idle":"2025-05-15T16:02:52.467431Z","shell.execute_reply.started":"2025-05-15T16:02:52.448126Z","shell.execute_reply":"2025-05-15T16:02:52.466792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SteganalysisModel(nn.Module):\n    def __init__(self, num_classes=2):\n        super(SteganalysisModel, self).__init__()\n        \n        self.dct = DCTLayer()\n        \n        # Pretrained EfficientNet B2\n        weights = EfficientNet_B2_Weights.DEFAULT\n        self.backbone = efficientnet_b2(weights=weights)\n        \n        # Thay đổi đầu vào Conv đầu tiên thành 9 channels\n        self.backbone.features[0][0] = nn.Conv2d(9, 32, kernel_size=3, stride=2, padding=1, bias=False)\n        \n        # Attention\n        self.attention = nn.Sequential(\n            nn.Conv2d(1408, 512, kernel_size=1),\n            nn.ReLU(),\n            nn.Conv2d(512, 1, kernel_size=1),\n            nn.Sigmoid()\n        )\n        \n        # Classifier\n        self.classifier = nn.Sequential(\n            nn.Linear(1408, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n        \n    def forward(self, x):\n        x = self.dct(x)                       \n        features = self.backbone.features(x) # EfficientNet features\n        attention_weights = self.attention(features)\n        features = features * attention_weights\n        features = F.adaptive_avg_pool2d(features, (1, 1))\n        features = torch.flatten(features, 1)\n        output = self.classifier(features)\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.566080Z","iopub.execute_input":"2025-05-15T16:02:52.566269Z","iopub.status.idle":"2025-05-15T16:02:52.572592Z","shell.execute_reply.started":"2025-05-15T16:02:52.566254Z","shell.execute_reply":"2025-05-15T16:02:52.571919Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Tạo tập dữ liệu","metadata":{}},{"cell_type":"code","source":"base_path = '../input/alaska2-image-steganalysis'\n\ndef read_images_path(dir_name, label):\n    folder_path = os.path.join(base_path, dir_name)\n    return [[os.path.join(folder_path, filename), label] for filename in os.listdir(folder_path)]\n\ndef prepare_balanced_dataset(sample_size=15000):\n    # Load cover images\n    cover_img = read_images_path('Cover', 0)[:sample_size]\n    \n    # Load stego images (balanced among different techniques)\n    stego_per_type = sample_size\n    jmipod_img = read_images_path('JMiPOD', 1)[:stego_per_type]\n    juniward_img = read_images_path('JUNIWARD', 1)[:stego_per_type]\n    uerd_img = read_images_path('UERD', 1)[:stego_per_type]\n    \n    # Combine all images\n    data = cover_img + jmipod_img + juniward_img + uerd_img\n    df = pd.DataFrame(data=data, columns=['path', 'label'])\n    \n    # Shuffle data\n    df = df.sample(frac=1, random_state=42).reset_index(drop=True)\n    \n    # Split into train and validation\n    train_size = int(len(df) * 0.8)\n    train = df.iloc[:train_size]\n    val = df.iloc[train_size:]\n    \n    return train, val","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.573648Z","iopub.execute_input":"2025-05-15T16:02:52.573814Z","iopub.status.idle":"2025-05-15T16:02:52.593783Z","shell.execute_reply.started":"2025-05-15T16:02:52.573801Z","shell.execute_reply":"2025-05-15T16:02:52.592984Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Weight AUC","metadata":{}},{"cell_type":"code","source":"def weighted_auc(y_true, y_score, tpr_thresholds=[0.0, 0.4, 1.0], weights=[2, 1]):\n    fpr, tpr, _ = roc_curve(y_true, y_score)\n    \n    # Calculate AUC for each region\n    auc_scores = []\n    for i in range(len(tpr_thresholds) - 1):\n        start = tpr_thresholds[i]\n        end = tpr_thresholds[i+1]\n        \n        # Filter points in current region\n        mask = (tpr >= start) & (tpr < end)\n        auc_scores.append(np.trapz(tpr[mask], fpr[mask]))\n    \n    # Calculate weighted AUC\n    weighted_auc = np.sum(np.multiply(auc_scores, weights)) / np.sum(weights)\n    \n    return weighted_auc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.595052Z","iopub.execute_input":"2025-05-15T16:02:52.595247Z","iopub.status.idle":"2025-05-15T16:02:52.613875Z","shell.execute_reply.started":"2025-05-15T16:02:52.595233Z","shell.execute_reply":"2025-05-15T16:02:52.613257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def calculate_metrics(model, dataloader, device='cpu'):\n    model.to(device).eval()\n    labels, probs, preds = [], [], []\n\n    with torch.no_grad():\n        for images, batch_labels in dataloader:\n            outputs = model(images.to(device))\n            batch_probs = F.softmax(outputs, dim=1)[:, 1]\n            batch_preds = torch.argmax(outputs, dim=1)\n\n            labels.extend(batch_labels.cpu().numpy())\n            probs.extend(batch_probs.cpu().numpy())\n            preds.extend(batch_preds.cpu().numpy())\n\n    labels = np.array(labels)\n    probs = np.array(probs)\n    preds = np.array(preds)\n\n    return {\n        'Weighted AUC': weighted_auc(labels, probs),\n        'Accuracy': accuracy_score(labels, preds)\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.614623Z","iopub.execute_input":"2025-05-15T16:02:52.614876Z","iopub.status.idle":"2025-05-15T16:02:52.629977Z","shell.execute_reply.started":"2025-05-15T16:02:52.614856Z","shell.execute_reply":"2025-05-15T16:02:52.629269Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Thiết lập ","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n# Import OpenCV for noise extraction\nimport cv2\n\n# Set hyperparameters\nBATCH_SIZE = 16  # Smaller batch size for better gradient updates\nEPOCHS = 10  # More epochs for better training\nLR = 10e-4  # Learning rate\nWEIGHT_DECAY = 1e-5  # Add regularization","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.631377Z","iopub.execute_input":"2025-05-15T16:02:52.631578Z","iopub.status.idle":"2025-05-15T16:02:52.651745Z","shell.execute_reply.started":"2025-05-15T16:02:52.631563Z","shell.execute_reply":"2025-05-15T16:02:52.651004Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Chuẩn bị dữ liệu","metadata":{}},{"cell_type":"code","source":"train, val = prepare_balanced_dataset(sample_size=20000)\n\n# Tạo tập dữ liệu\ntrain_dataset = SteganalysisDataset(train['path'].tolist(), train['label'].tolist(), augment=True)\nval_dataset = SteganalysisDataset(val['path'].tolist(), val['label'].tolist(), augment=False)\n\n# Tạo dataloader\ntrain_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, \n                              num_workers=4, pin_memory=True, prefetch_factor=2, persistent_workers=True)\nval_dataloader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, \n                           num_workers=4, pin_memory=True)\n\n# Tạo model\nmodel = SteganalysisModel(num_classes=2).to(device)\n\n# Thiết lập trọng số lớp \nWEIGHTS = torch.tensor([3, 1], dtype=torch.float32).to(device)\n\n# Tối ưu hóa cho suy giảm trọng số\noptimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n\n# Learning rate scheduler\nscheduler = torch.optim.lr_scheduler.OneCycleLR(\n    optimizer,\n    max_lr=1e-4,\n    epochs=EPOCHS,\n    steps_per_epoch=len(train_dataloader),\n    pct_start=0.3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:52.652317Z","iopub.execute_input":"2025-05-15T16:02:52.652521Z","iopub.status.idle":"2025-05-15T16:02:55.878576Z","shell.execute_reply.started":"2025-05-15T16:02:52.652507Z","shell.execute_reply":"2025-05-15T16:02:55.878004Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Thực hiện training","metadata":{}},{"cell_type":"code","source":"train_losses = []\naccuracies = []\nweighted_aucs = []\nbest_auc = 0\n\nfor epoch in range(EPOCHS):\n    print(f'EPOCH: {epoch+1}/{EPOCHS}')\n    if torch.cuda.is_available():\n        print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n        print(f\"Memory allocated: {torch.cuda.memory_allocated(0)/1e9:.2f} GB\")\n        print(f\"Memory reserved: {torch.cuda.memory_reserved(0)/1e9:.2f} GB\")\n    \n    # Training phase\n    model.train()\n    train_loss = 0\n    \n    for images, labels in train_dataloader:\n        images, labels = images.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        \n        loss = F.cross_entropy(outputs, labels, weight=WEIGHTS)\n        loss.backward()\n        \n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        optimizer.step()\n        \n        scheduler.step()  # <== CHUẨN OneCycleLR: step mỗi batch\n        \n        train_loss += loss.item()\n        \n    avg_train_loss = train_loss / len(train_dataloader)\n    train_losses.append(avg_train_loss)\n    print(f'Average Training Loss: {avg_train_loss:.4f}')\n    \n    # Validation\n    model.eval()\n    metrics = calculate_metrics(model, val_dataloader, device)\n    acc = metrics['Accuracy']\n    current_auc = metrics['Weighted AUC']\n    \n    accuracies.append(acc)\n    weighted_aucs.append(current_auc)\n    \n    print(f'Validation Accuracy: {acc:.4f}')\n    print(f'Validation Weighted AUC: {current_auc:.4f}')\n    \n    # Save best model\n    if current_auc > best_auc:\n        best_auc = current_auc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T16:02:55.879284Z","iopub.execute_input":"2025-05-15T16:02:55.879615Z","iopub.status.idle":"2025-05-15T20:30:07.434576Z","shell.execute_reply.started":"2025-05-15T16:02:55.879592Z","shell.execute_reply":"2025-05-15T20:30:07.433504Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Thống kê kết quả","metadata":{}},{"cell_type":"code","source":" # Plot training metrics\nplt.figure(figsize=(15, 5))\n\n# Plot Loss\nplt.subplot(1, 3, 1)\nplt.plot(range(1, EPOCHS+1), train_losses, marker='o', label='Training Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss per Epoch')\nplt.legend()\n\n# Plot Accuracy\nplt.subplot(1, 3, 2)\nplt.plot(range(1, EPOCHS+1), accuracies, marker='o', label='Accuracy', color='green')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Accuracy per Epoch')\nplt.legend()\n\n# Plot Weighted AUC\nplt.subplot(1, 3, 3)\nplt.plot(range(1, EPOCHS+1), weighted_aucs, marker='o', label='Weighted AUC', color='red')\nplt.xlabel('Epoch')\nplt.ylabel('AUC')\nplt.title('Weighted AUC per Epoch')\nplt.legend()\n\nplt.tight_layout()\nplt.savefig('training_metrics.png')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T20:30:07.435991Z","iopub.execute_input":"2025-05-15T20:30:07.436400Z","iopub.status.idle":"2025-05-15T20:30:08.295406Z","shell.execute_reply.started":"2025-05-15T20:30:07.436364Z","shell.execute_reply":"2025-05-15T20:30:08.294775Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dự đoán","metadata":{}},{"cell_type":"code","source":" # Prepare test predictions\ntest_img = read_images_path('Test', 0)\ntest = pd.DataFrame(data=test_img, columns=['path', 'label'])\n\n# Create test dataset and dataloader\ntest_dataset = SteganalysisDataset(test['path'].tolist(), test['label'].tolist(), augment=False)\ntest_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, \n                            num_workers=4, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T20:30:08.296275Z","iopub.execute_input":"2025-05-15T20:30:08.296791Z","iopub.status.idle":"2025-05-15T20:30:08.386301Z","shell.execute_reply.started":"2025-05-15T20:30:08.296766Z","shell.execute_reply":"2025-05-15T20:30:08.385674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict(model, dataloader, device):\n    model.eval()\n    predictions = []\n    \n    with torch.no_grad():\n        for images, _ in dataloader:\n            images = images.to(device)\n            outputs = model(images)\n            scores = F.softmax(outputs, dim=1)[:, 1]  # Probability of being stego\n            predictions.extend(scores.cpu().numpy())\n            \n    return predictions\n\n# Generate predictions\nprint(\"Generating predictions...\")\ntest_predictions = predict(model, test_dataloader, device)\nprint(\"Predictions complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T20:30:08.387194Z","iopub.execute_input":"2025-05-15T20:30:08.387798Z","iopub.status.idle":"2025-05-15T20:30:49.577781Z","shell.execute_reply.started":"2025-05-15T20:30:08.387781Z","shell.execute_reply":"2025-05-15T20:30:49.576923Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"# Create submission file\ntest_image_ids = [os.path.basename(path) for path in test_dataset.image_paths]\nsubmission_df = pd.DataFrame({\n    'Id': test_image_ids,\n    'Label': test_predictions\n})\n\n# Format IDs as required by competition\nsubmission_df['Id'] = submission_df['Id'].str.replace('.jpg', '').astype(int)\nsubmission_df = submission_df.sort_values(by='Id')\nsubmission_df['Id'] = submission_df['Id'].astype(str).str.zfill(4) + '.jpg'\n\n# Save submission file\nsubmission_df.to_csv('submission.csv', index=False)\nprint(\"Submission file created: submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-15T20:30:49.580159Z","iopub.execute_input":"2025-05-15T20:30:49.580374Z","iopub.status.idle":"2025-05-15T20:30:49.617830Z","shell.execute_reply.started":"2025-05-15T20:30:49.580356Z","shell.execute_reply":"2025-05-15T20:30:49.617246Z"}},"outputs":[],"execution_count":null}]}