{"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":19991,"databundleVersionId":1117522,"sourceType":"competition"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### 1. Giới thiệu chung về đồ án\n\nTrong thời đại số hiện nay, việc bảo vệ thông tin và phát hiện các hành vi che giấu dữ liệu trở nên vô cùng quan trọng. Một trong những kỹ thuật được sử dụng để giấu dữ liệu một cách bí mật trong nội dung đa phương tiện là **steganography (kỹ thuật giấu tin)**. Đặc biệt, hình ảnh là một môi trường phổ biến để nhúng dữ liệu nhờ vào sự phong phú về thông tin và khả năng che giấu cao.\n\nĐồ án này tập trung vào bài toán **phân tích giấu tin (steganalysis)**, cụ thể là phân biệt giữa các ảnh **gốc (cover)** và ảnh đã được **giấu dữ liệu (stego)**. Mục tiêu là xây dựng một mô hình học sâu (deep learning) có khả năng phát hiện chính xác các dấu hiệu giấu tin dù rất nhỏ, giúp phục vụ cho các ứng dụng như: bảo mật thông tin, giám sát nội dung và điều tra số.\n\nĐể giải quyết bài toán, nhóm đã kết hợp:\n- Các **bộ lọc SRM (Spatial Rich Model)** – giúp trích xuất đặc trưng nhiễu ẩn trong ảnh.\n- Mạng **EfficientNet-B2** với các tinh chỉnh đầu vào để xử lý ảnh đã qua SRM.\n- Cơ chế **attention** để tăng khả năng tập trung vào vùng ảnh nghi ngờ.\n- Phương pháp huấn luyện hiệu quả với **OneCycleLR**, dữ liệu tăng cường (augmentation) và đánh giá bằng chỉ số **Weighted AUC**.\n\nBộ dữ liệu sử dụng gồm các ảnh được giấu tin bởi nhiều kỹ thuật khác nhau như **JMiPOD, JUNIWARD, UERD**, đảm bảo mô hình có tính tổng quát và khả năng phát hiện tốt trên các phương pháp giấu tin phổ biến.","metadata":{}},{"cell_type":"markdown","source":"### 2. Từng bước chạy chương trình","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 tqdm import tqdm\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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:05:32.746038Z","iopub.execute_input":"2025-04-15T21:05:32.746321Z","iopub.status.idle":"2025-04-15T21:05:43.823698Z","shell.execute_reply.started":"2025-04-15T21:05:32.746298Z","shell.execute_reply":"2025-04-15T21:05:43.823152Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"`NumPy`: Thư viện hỗ trợ các phép toán mảng (arrays) hiệu quả, rất cần thiết trong xử lý dữ liệu số. Dùng để xử lý dữ liệu đầu vào/đầu ra dưới dạng mảng, đặc biệt trong huấn luyện mô hình.\n\n`Pandas`: Thư viện để thao tác dữ liệu dạng bảng (DataFrame, CSV, v.v.). Dùng để đọc, xử lý và phân tích dữ liệu từ file .csv hoặc nguồn dữ liệu dạng bảng khác.\n\n`PyTorch`: Thư viện học sâu được dùng để xây dựng và huấn luyện mô hình AI. Đây là thư viện cốt lõi để xử lý tensor và huấn luyện mạng nơ-ron.\n\n`os`: Thư viện chuẩn trong Python để làm việc với hệ điều hành. Thường dùng để lấy đường dẫn tệp tin, kiểm tra file tồn tại, hoặc duyệt thư mục.\n\n`torch.nn`: Cung cấp các lớp để xây dựng mạng nơ-ron, như Linear, Conv2d, Dropout,... Đây là nơi định nghĩa cấu trúc mô hình.\n\n`torch.nn.functional`: Cung cấp các hàm kích hoạt và hàm loss như F.relu, F.cross_entropy,... Dùng khi bạn muốn dùng hàm mà không cần định nghĩa layer như nn.ReLU().\n\n`matplotlib.pyplot`: Thư viện vẽ biểu đồ (rất phổ biến). Dùng để vẽ đồ thị loss, accuracy, ROC curve, hình ảnh...\n\n`tqdm`: Thêm thanh tiến trình khi lặp (for loop). Giúp bạn theo dõi quá trình train/test mất bao lâu, chạy đến đâu.\n\n`PIL (Python Imaging Library)`: Dùng để mở và xử lý hình ảnh. Quan trọng khi bạn làm việc với dataset ảnh (đọc ảnh từ ổ đĩa, resize...).\n\n`torchvision.models.efficientnet`: \n\n**efficientnet_b2**: Import mô hình EfficientNet-B2, một kiến trúc CNN tối ưu hiệu suất và kích thước. Đây là backbone chính của mô hình trong bài toán phân loại ảnh bạn đang làm.\n\n**EfficientNet_B2_Weights**: là enum (tập lựa chọn) các bộ trọng số pre-trained cho EfficientNet-B2. Dùng để chỉ định trọng số đã huấn luyện\n\n`torch.utils.data`: Dùng để định nghĩa dataset tùy chỉnh (Dataset) và bộ tải dữ liệu (DataLoader). Giúp mô hình đọc ảnh + nhãn từ thư mục, chia batch, shuffle, v.v.\n\n`sklearn.metrics`:\n\n**accuracy_score**: Hàm tính độ chính xác (số mẫu đúng / tổng số mẫu).\n\n**roc_curve**: Tính toán điểm để vẽ ROC curve – dùng đánh giá chất lượng phân loại nhị phân.","metadata":{}},{"cell_type":"code","source":"base_path = '../input/alaska2-image-steganalysis'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:05:48.213471Z","iopub.execute_input":"2025-04-15T21:05:48.214150Z","iopub.status.idle":"2025-04-15T21:05:48.217891Z","shell.execute_reply.started":"2025-04-15T21:05:48.214125Z","shell.execute_reply":"2025-04-15T21:05:48.217269Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Thiết lập đường dẫn cơ sở (base path) đến thư mục chứa dữ liệu của bài toán","metadata":{}},{"cell_type":"code","source":"def 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)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:05:49.975433Z","iopub.execute_input":"2025-04-15T21:05:49.976037Z","iopub.status.idle":"2025-04-15T21:05:49.980263Z","shell.execute_reply.started":"2025-04-15T21:05:49.976003Z","shell.execute_reply":"2025-04-15T21:05:49.979691Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Đây là hàm dùng để lấy danh sách đường dẫn ảnh và gán nhãn tương ứng cho mỗi ảnh.  \nCụ thể:  \n- `dir_name`: tên thư mục chứa ảnh\n- `label`: nhãn gán cho tất cả ảnh trong thư mục đó\n\nHàm duyệt qua tất cả các file trong thư mục và trả về danh sách dạng:  \n`[[đường_dẫn_ảnh, nhãn], ...]`\n\n→ Dữ liệu sau đó sẽ được dùng để huấn luyện mô hình.","metadata":{}},{"cell_type":"code","source":"def preprocess_image(image_path, size=(512, 512)):\n    # Load image\n    img = Image.open(image_path)\n    \n    # Convert to YCbCr color space (better for detecting steganography)\n    img = img.convert(\"YCbCr\")\n    \n    # Convert to numpy array with high precision (float64 to preserve subtle details)\n    img_array = np.array(img, dtype=np.float64)\n    \n    # Extract DCT residuals to better detect steganography artifacts\n    # This helps highlight the noise patterns that steganography introduces\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    # Transpose channels from (H, W, C) to (C, H, W) for PyTorch\n    img_array = np.transpose(img_array, (2, 0, 1))\n    \n    # Normalize to [0, 1] range but avoid standard ImageNet normalization\n    # which could destroy subtle steganographic patterns\n    img_array = img_array / 255.0\n    \n    # Convert to 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-04-15T21:05:52.334224Z","iopub.execute_input":"2025-04-15T21:05:52.334465Z","iopub.status.idle":"2025-04-15T21:05:52.340073Z","shell.execute_reply.started":"2025-04-15T21:05:52.334448Z","shell.execute_reply":"2025-04-15T21:05:52.339361Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm này được dùng để **tiền xử lý ảnh** trước khi đưa vào mô hình học sâu, đặc biệt là bài toán **phát hiện steganography** (giấu tin trong ảnh).\n\n\n### Các bước tiền xử lý:\n\n1. **Đọc ảnh**: \n- Mở ảnh từ đường dẫn `image_path` bằng `PIL.Image`.\n   \n2. **Chuyển đổi không gian màu**:\n- Chuyển ảnh từ không gian màu RGB sang không gian màu **YCbCr**, nơi **kênh Y (luminance)** giúp nhận diện các dấu hiệu steganography tốt hơn.\n\n3. **Chuyển ảnh thành mảng numpy**:\n- Chuyển ảnh thành mảng `numpy` với kiểu dữ liệu `float64` để giữ lại các chi tiết chính xác.\n\n4. **Trích xuất DCT residuals**:\n- Lấy kênh Y (luminance) và trừ đi **Gaussian blur** để làm nổi bật các artefacts (dấu vết) do steganography gây ra.\n\n5. **Chuyển đổi kênh**:\n- Đổi thứ tự các chiều mảng từ `(H, W, C)` (chiều cao, chiều rộng, số kênh) sang `(C, H, W)` (theo yêu cầu của PyTorch).\n\n6. **Chuẩn hóa giá trị pixel**:\n- Đưa các giá trị pixel về phạm vi `[0, 1]` để phù hợp với mô hình học sâu mà không làm mất đi các chi tiết quan trọng.\n\n7. **Chuyển đổi thành tensor PyTorch**:\n- Cuối cùng, chuyển mảng numpy thành tensor PyTorch với kiểu dữ liệu `float32` để sử dụng trong mô hình.","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        # Process image\n        image = preprocess_image(img_path)\n        \n        # Apply data augmentation (only for training)\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-04-15T21:05:56.094070Z","iopub.execute_input":"2025-04-15T21:05:56.094567Z","iopub.status.idle":"2025-04-15T21:05:56.100300Z","shell.execute_reply.started":"2025-04-15T21:05:56.094543Z","shell.execute_reply":"2025-04-15T21:05:56.099603Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"`SteganalysisDataset` kế thừa lớp `Dataset` từ PyTorch, giúp quản lý dữ liệu và cung cấp các phương thức để tải ảnh và nhãn cho quá trình huấn luyện mô hình.\n\n### Các phương thức trong class:\n\n1. **`__init__(self, image_paths, labels, augment=False)`**:\n- **Mục đích**: Khởi tạo dataset với danh sách đường dẫn ảnh (`image_paths`), nhãn (`labels`), và tùy chọn tăng cường dữ liệu (`augment`).\n- **Parameters**:\n     - `image_paths`: Danh sách các đường dẫn đến ảnh.\n     - `labels`: Danh sách các nhãn tương ứng với ảnh.\n     - `augment`: Biến boolean cho phép tăng cường dữ liệu (augmentation) trong quá trình huấn luyện.\n\n2. **`__len__(self)`**:\n- **Mục đích**: Trả về số lượng ảnh trong dataset.\n- **Kết quả**: Số lượng ảnh tương ứng với chiều dài của danh sách `image_paths`.\n\n3. **`__getitem__(self, index)`**:\n- **Mục đích**: Lấy ảnh và nhãn tại chỉ số `index`.\n- **Quy trình**:\n     - **Lấy đường dẫn ảnh**: Dựa trên chỉ số `index`, lấy đường dẫn của ảnh từ `image_paths`.\n     - **Tiền xử lý ảnh**: Sử dụng hàm `preprocess_image()` để xử lý ảnh.\n     - **Tăng cường dữ liệu (augmentation)**: Nếu `augment=True`, thực hiện các kỹ thuật tăng cường dữ liệu:\n       - **Lật ảnh ngang**: Với xác suất 50%, lật ảnh theo chiều ngang.\n       - **Cắt ngẫu nhiên**: Với xác suất 50%, cắt ngẫu nhiên một phần ảnh và resize lại để duy trì kích thước 512x512.\n   - **Trả về**: Ảnh đã được xử lý và nhãn tương ứng.","metadata":{}},{"cell_type":"code","source":"class SRMConv2D(nn.Module):\n    def __init__(self):\n        super(SRMConv2D, self).__init__()\n        \n        # Define SRM kernels (based on steganalysis research)\n        kernel1 = torch.tensor([[-1, 2, -1], [2, -4, 2], [-1, 2, -1]], dtype=torch.float32)\n        kernel2 = torch.tensor([[-1, 2, -1], [2, -4, 2], [0, 0, 0]], dtype=torch.float32)\n        kernel3 = torch.tensor([[0, 0, 0], [0, 1, 0], [0, 0, 0]], dtype=torch.float32)\n        \n        # Combine kernels into filter bank\n        self.srm_kernels = nn.Parameter(torch.stack([kernel1, kernel2, kernel3]).unsqueeze(1), \n                                       requires_grad=False)\n        \n        # Define the convolution operation\n        self.conv = nn.Conv2d(1, 3, kernel_size=3, padding=1, bias=False)\n        self.conv.weight = nn.Parameter(self.srm_kernels, requires_grad=False)\n        \n    def forward(self, x):\n        # Apply SRM filters independently to each channel\n        y_channel = self.conv(x[:, 0:1, :, :])\n        cb_channel = self.conv(x[:, 1:2, :, :])\n        cr_channel = self.conv(x[:, 2:3, :, :])\n        \n        # Combine results\n        return torch.cat([y_channel, cb_channel, cr_channel], dim=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:05:59.064370Z","iopub.execute_input":"2025-04-15T21:05:59.064871Z","iopub.status.idle":"2025-04-15T21:05:59.071444Z","shell.execute_reply.started":"2025-04-15T21:05:59.064848Z","shell.execute_reply":"2025-04-15T21:05:59.070666Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"`SRMConv2D` là một lớp mạng nơ-ron (CNN) sử dụng **một bộ lọc SRM (Spatial Rich Model)**, được thiết kế đặc biệt cho **phân tích steganography**. Bộ lọc SRM này giúp phát hiện các dấu hiệu do việc giấu tin (steganography) gây ra.\n\n### Các thành phần trong class:\n\n1. **Khởi tạo `__init__(self)`**:\n- **SRM kernels**: Khởi tạo ba **kernel SRM** (mô phỏng các bộ lọc phổ biến trong nghiên cứu về steganalysis). Mỗi kernel là một ma trận 3x3, đặc trưng cho các mẫu không gian và được tạo dưới dạng tensor `torch`.\n- **Bộ lọc SRM**: Kết hợp các kernel vào một **filter bank** bằng cách sử dụng `nn.Parameter` (để đảm bảo rằng các kernel không thay đổi trong quá trình huấn luyện).\n- **Convolution layer**: Khởi tạo lớp `Conv2d` với một kênh đầu vào và ba kênh đầu ra (tương ứng với ba kernel), sử dụng các kernel SRM đã định nghĩa.\n\n2. **`forward(self, x)`**:\n- **Áp dụng bộ lọc SRM**: Lớp `forward` áp dụng bộ lọc SRM cho từng kênh của ảnh (Y, Cb, Cr) một cách độc lập:\n     - Kênh Y (luminance) được xử lý với kernel đầu tiên.\n     - Kênh Cb và Cr (chrominance) được xử lý với kernel thứ hai và thứ ba.\n- **Kết hợp kết quả**: Kết quả của ba kênh được kết hợp lại để tạo thành tensor có ba kênh đầu ra.","metadata":{}},{"cell_type":"code","source":"class SteganalysisModel(nn.Module):\n    def __init__(self, num_classes=2):\n        super(SteganalysisModel, self).__init__()\n        \n        # SRM preprocessing layer\n        self.srm = SRMConv2D()\n        \n        # Load pretrained EfficientNet with updated API\n        weights = EfficientNet_B2_Weights.DEFAULT  # hoặc IMAGENET1K_V1 nếu bạn cần bản cố định\n        self.backbone = efficientnet_b2(weights=weights)\n        \n        # Modify first layer to accept 9 channels\n        self.backbone.features[0][0] = nn.Conv2d(9, 32, kernel_size=3, stride=2, padding=1, bias=False)\n        \n        # Modified classifier with 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        # Final 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.srm(x)\n        features = self.backbone.features(x)\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\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:06:01.441181Z","iopub.execute_input":"2025-04-15T21:06:01.442256Z","iopub.status.idle":"2025-04-15T21:06:01.448853Z","shell.execute_reply.started":"2025-04-15T21:06:01.442219Z","shell.execute_reply":"2025-04-15T21:06:01.448060Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"`SteganalysisModel` là một mô hình học sâu sử dụng **SRM preprocessing**, **EfficientNet-B2** (một mạng CNN tiền huấn luyện) và **cơ chế attention** để phân tích ảnh và phát hiện dấu vết steganography.\n\n### Các thành phần trong class:\n\n1. **Khởi tạo `__init__(self, num_classes=2)`**:\n- **SRM Preprocessing Layer**: Lớp `SRMConv2D` được sử dụng để xử lý ảnh, giúp phát hiện các dấu vết steganography trước khi đưa vào mô hình.\n- **Backbone (EfficientNet-B2)**: \n     - Sử dụng EfficientNet-B2 đã được huấn luyện sẵn với trọng số từ `EfficientNet_B2_Weights.DEFAULT`.\n     - Thay đổi lớp đầu tiên của mô hình EfficientNet để chấp nhận **9 kênh đầu vào** (thay vì 3 kênh như bình thường) vì đầu vào đã được xử lý qua SRM (3 kênh YCbCr + 3 bộ lọc SRM).\n- **Attention Layer**:\n     - Bao gồm các lớp convolutional và sigmoid để áp dụng **cơ chế attention** lên các đặc trưng (features) của mô hình, giúp mô hình tập trung vào các khu vực quan trọng trong ảnh.\n- **Final Classifier**:\n     - Một lớp fully-connected (FC) để phân loại các đặc trưng sau khi áp dụng attention. Bao gồm các bước: Linear → BatchNorm → ReLU → Dropout → Linear.\n\n2. **`forward(self, x)`**:\n- **Tiền xử lý SRM**: Ảnh đầu vào được xử lý qua lớp `SRMConv2D` để trích xuất các đặc trưng steganographic.\n- **Trích xuất đặc trưng**: Các đặc trưng của ảnh được trích xuất từ mô hình EfficientNet-B2.\n- **Cơ chế Attention**: Áp dụng attention lên các đặc trưng đã trích xuất, giúp mô hình tập trung vào các vùng quan trọng.\n- **Adaptive Pooling và Flattening**: Sử dụng `adaptive_avg_pool2d` để giảm kích thước đặc trưng về 1x1, sau đó flatten các đặc trưng này.\n- **Phân loại**: Các đặc trưng đã được xử lý cuối cùng được đưa vào lớp phân loại để tạo ra đầu ra dự đoán.","metadata":{}},{"cell_type":"code","source":"def 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-04-15T21:06:04.013986Z","iopub.execute_input":"2025-04-15T21:06:04.014269Z","iopub.status.idle":"2025-04-15T21:06:04.019518Z","shell.execute_reply.started":"2025-04-15T21:06:04.014247Z","shell.execute_reply":"2025-04-15T21:06:04.018691Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm này tạo ra một **dataset cân bằng** giữa ảnh gốc và ảnh đã qua xử lý steganography, phân chia dữ liệu thành **train** và **validation** để phục vụ quá trình huấn luyện mô hình.\n\n### Các bước trong hàm:\n\n1. **Tải ảnh gốc (Cover images)**:\n- Dùng hàm `read_images_path('Cover', 0)` để tải ảnh gốc (label 0) từ thư mục `Cover`, giới hạn số lượng ảnh bằng `sample_size`.\n\n2. **Tải ảnh đã giấu tin (Stego images)**:\n- Tải ảnh từ ba kỹ thuật giấu tin khác nhau: `JMiPOD`, `JUNIWARD`, và `UERD`. Mỗi loại được giới hạn số lượng bằng `sample_size`.\n\n3. **Kết hợp dữ liệu**:\n- Kết hợp ảnh gốc và ảnh stego vào một danh sách và chuyển thành **DataFrame** để dễ dàng thao tác.\n\n4. **Xáo trộn dữ liệu**:\n- Dữ liệu được xáo trộn (shuffle) với xác suất 100% (`frac=1`) để tránh sự thiên lệch trong dữ liệu và đảm bảo tính ngẫu nhiên.\n\n5. **Chia dữ liệu thành train và validation**:\n- Dữ liệu được chia thành 80% cho tập huấn luyện (`train`) và 20% cho tập kiểm tra (`val`).","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-04-15T21:06:06.642644Z","iopub.execute_input":"2025-04-15T21:06:06.642888Z","iopub.status.idle":"2025-04-15T21:06:06.648015Z","shell.execute_reply.started":"2025-04-15T21:06:06.642871Z","shell.execute_reply":"2025-04-15T21:06:06.647322Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm `weighted_auc` dùng để **đánh giá hiệu suất mô hình** bằng cách tính **AUC có trọng số (Weighted AUC)** – một chỉ số tùy biến hơn so với AUC thông thường, đặc biệt hữu ích trong bài toán steganalysis trên Kaggle.\n\n### Giải thích từng bước:\n\n1. **`roc_curve(y_true, y_score)`**:\n- Tính toán đường cong ROC từ nhãn thật và xác suất dự đoán.\n- Trả về: `fpr` (False Positive Rate), `tpr` (True Positive Rate).\n\n2. **Tách ROC theo từng khoảng `TPR`**:\n- Theo mặc định, chia thành 2 vùng: `[0.0 - 0.4]` và `[0.4 - 1.0]`.\n- Mỗi vùng mang trọng số khác nhau (`weights=[2, 1]`): vùng đầu tiên quan trọng gấp đôi vùng sau.\n\n3. **Tính diện tích (AUC) từng vùng**:\n- Với mỗi vùng, lọc các điểm trên ROC curve thuộc khoảng đó và tính diện tích bằng `np.trapz()` (phép tích phân gần đúng).\n\n4. **Tính AUC tổng có trọng số**:\n- Dùng công thức trung bình có trọng số để cộng dồn các AUC từng vùng lại.","metadata":{}},{"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-04-15T21:06:09.764726Z","iopub.execute_input":"2025-04-15T21:06:09.765336Z","iopub.status.idle":"2025-04-15T21:06:09.770544Z","shell.execute_reply.started":"2025-04-15T21:06:09.765315Z","shell.execute_reply":"2025-04-15T21:06:09.769742Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Hàm `calculate_metrics` được dùng để **đánh giá mô hình** sau khi huấn luyện, trả về hai chỉ số chính:\n\n- `Weighted AUC`: đo lường hiệu quả phân loại có trọng số (phù hợp với yêu cầu của Kaggle ALASKA2).\n- `Accuracy`: độ chính xác tổng thể của mô hình.\n\n###  Giải thích từng bước:\n\n1. **Chuyển mô hình sang chế độ đánh giá (`eval`)**:\n- Tắt dropout, batchnorm update.\n- Chuyển mô hình sang thiết bị CPU hoặc GPU.\n\n2. **Lặp qua từng batch trong `dataloader`**:\n- Với mỗi batch ảnh và nhãn:\n     - Dự đoán đầu ra bằng mô hình.\n     - Tính xác suất nhãn “1” bằng `softmax`.\n     - Lấy nhãn dự đoán (class có xác suất cao nhất).\n\n3. **Tổng hợp tất cả kết quả**:\n- Lưu lại các nhãn thật (`labels`), xác suất (`probs`), và dự đoán (`preds`).\n\n4. **Tính toán các chỉ số**:\n- `Weighted AUC`: dùng hàm `weighted_auc()` đã định nghĩa trước.\n- `Accuracy`: dùng hàm `accuracy_score()` từ sklearn.","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 = 5e-5  # Learning rate\nWEIGHT_DECAY = 1e-5  # Add regularization","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:06:12.810175Z","iopub.execute_input":"2025-04-15T21:06:12.810689Z","iopub.status.idle":"2025-04-15T21:06:13.178335Z","shell.execute_reply.started":"2025-04-15T21:06:12.810670Z","shell.execute_reply":"2025-04-15T21:06:13.177589Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Đoạn code này thiết lập môi trường huấn luyện và các **siêu tham số (hyperparameters)** cho quá trình huấn luyện mô hình phân tích ảnh giấu tin.\n\n### Giải thích từng dòng:\n\n1. **`device = torch.device(...)`**:\n- Kiểm tra nếu GPU (CUDA) khả dụng → sử dụng GPU để tăng tốc huấn luyện.\n- Ngược lại dùng CPU.\n\n2. **`print(...)`**:\n- In ra thiết bị đang sử dụng để theo dõi.\n\n3. **`import cv2`**:\n- Import thư viện OpenCV, dùng cho việc xử lý ảnh (đặc biệt là lọc nhiễu trong hàm `preprocess_image`).\n\n4. **Thiết lập siêu tham số**:\n- `BATCH_SIZE = 16`: kích thước mỗi batch nhỏ để cập nhật trọng số ổn định hơn.\n- `EPOCHS = 10`: số lần huấn luyện toàn bộ tập dữ liệu.\n- `LR = 5e-5`: tốc độ học (learning rate) nhỏ giúp mô hình học ổn định hơn.\n- `WEIGHT_DECAY = 1e-5`: giúp **regularization** (chống overfitting) bằng cách phạt trọng số lớn.","metadata":{}},{"cell_type":"code","source":"# Prepare datasets\nprint(\"Preparing datasets...\")\ntrain, val = prepare_balanced_dataset(sample_size=20000)\n\n# Create datasets\ntrain_dataset = SteganalysisDataset(train['path'].tolist(), train['label'].tolist(), augment=True)\nval_dataset = SteganalysisDataset(val['path'].tolist(), val['label'].tolist(), augment=False)\n\n# Create dataloaders\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# Create model\nmodel = SteganalysisModel(num_classes=2).to(device)\n\n# Set up class weights to handle imbalance\n# Higher weight for stego images (class 1)\nWEIGHTS = torch.tensor([3, 1], dtype=torch.float32).to(device)\n\n# Create optimizer with weight decay\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-04-15T21:46:27.437823Z","iopub.execute_input":"2025-04-15T21:46:27.438355Z","iopub.status.idle":"2025-04-15T21:46:30.287023Z","shell.execute_reply.started":"2025-04-15T21:46:27.438332Z","shell.execute_reply":"2025-04-15T21:46:30.286492Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Chuẩn bị tập dữ liệu, mô hình và các thành phần huấn luyện như bộ tối ưu, trọng số lớp, và bộ điều chỉnh tốc độ học.\n\n### Giải thích chi tiết từng bước:\n\n1. **Tạo tập dữ liệu huấn luyện & kiểm thử:**\n\n\n   `train, val = prepare_balanced_dataset(sample_size=20000)`\n- Lấy ngẫu nhiên 20,000 ảnh từ mỗi loại (cover và stego).\n- Chia theo tỷ lệ 80% train / 20% validation.\n\n2. **Tạo `Dataset` và `DataLoader`:**\n\n\n   `train_dataset = SteganalysisDataset(...)`\n\n\n   `val_dataset = SteganalysisDataset(...)`\n- Dữ liệu train có bật `augment=True` (tăng cường dữ liệu).\n- Dataloader dùng `num_workers=4` để tải dữ liệu nhanh hơn, `pin_memory=True` để tối ưu trên GPU.\n\n3. **Khởi tạo mô hình:**\n\n\n   `model = SteganalysisModel(...)`\n- Mô hình sử dụng EfficientNet-B2 kết hợp lớp SRMConv để tăng cường khả năng phát hiện giấu tin.\n\n4. **Thiết lập trọng số lớp (`class weights`):**\n\n\n   `WEIGHTS = torch.tensor([3, 1], ...)`\n- Áp dụng trọng số lớn hơn cho class stego (1) nhằm cân bằng tập dữ liệu, giúp mô hình chú ý hơn đến ảnh có giấu tin.\n\n5. **Tạo Optimizer (AdamW):**\n\n\n   `optimizer = torch.optim.AdamW(...)`\n- AdamW kết hợp với `weight_decay` để tránh overfitting.\n\n6. **Scheduler - OneCycleLR:**\n\n\n   `scheduler = torch.optim.lr_scheduler.OneCycleLR(...)`\n- Tăng giảm tốc độ học theo chu kỳ → giúp mô hình hội tụ tốt hơn.","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 tqdm(train_dataloader, desc=\"Training\"):\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\n        torch.save(model.state_dict(), 'best_stego_model.pth')\n        print(f\"New best model saved with Weighted AUC: {best_auc:.4f}\")\n    \n    print()\n\n# Load best model\nmodel.load_state_dict(torch.load('best_stego_model.pth'))\nprint(\"Best model loaded!\")\n\n# Save full model\ntorch.save(model, 'complete_steganalysis_model.pth')\nprint(\"Complete model saved to 'complete_steganalysis_model.pth'\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-15T21:46:34.881150Z","iopub.execute_input":"2025-04-15T21:46:34.881451Z","iopub.status.idle":"2025-04-16T04:34:03.832991Z","shell.execute_reply.started":"2025-04-15T21:46:34.881429Z","shell.execute_reply":"2025-04-16T04:34:03.832142Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Vòng lặp huấn luyện & đánh giá mô hình:\n\nHuấn luyện mô hình `SteganalysisModel` với dữ liệu đã chuẩn bị, đánh giá hiệu quả trên tập kiểm thử sau mỗi epoch, và lưu lại mô hình có độ chính xác cao nhất (theo *Weighted AUC*).\n\n\n#### Các bước chính:\n\n1. **Khởi tạo theo dõi hiệu suất:**\n\n\n   `train_losses = []; accuracies = []; weighted_aucs = []; best_auc = 0`\n- Dùng để lưu lại kết quả sau mỗi epoch.\n\n2. **Huấn luyện từng epoch:**\n   \n   `for epoch in range(EPOCHS):\n       model.train()\n       ...`\n\n- Gọi `model.train()` để bật chế độ huấn luyện.\n- Với mỗi batch:\n    - Tính loss dùng `F.cross_entropy` (có `class weights`).\n    - Backpropagation & cập nhật trọng số.\n    - `clip_grad_norm_`: tránh gradient exploding.\n    - Gọi `scheduler.step()` **mỗi batch** (đúng với OneCycleLR).\n\n3. **Tính trung bình loss và lưu lại:**\n\n   `avg_train_loss = train_loss / len(train_dataloader)`\n\n   \n\n4. **Đánh giá trên tập validation:**\n\n   `model.eval()\n   metrics = calculate_metrics(...)`\n- Đánh giá `Accuracy` và `Weighted AUC`.\n- Hàm `calculate_metrics` sử dụng `roc_curve`, `accuracy_score`.\n\n5. **Lưu mô hình tốt nhất:**\n\n   `if current_auc > best_auc:\n       torch.save(model.state_dict(), ...)`\n- Chỉ lưu lại mô hình nếu đạt AUC cao nhất từ trước đến nay.\n\n6. **Lưu toàn bộ mô hình:**\n\n   `torch.save(model, 'complete_steganalysis_model.pth')`\n- Lưu cả kiến trúc + trọng số (khác với chỉ `state_dict`).","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-04-16T04:50:43.454518Z","iopub.execute_input":"2025-04-16T04:50:43.454874Z","iopub.status.idle":"2025-04-16T04:50:44.446143Z","shell.execute_reply.started":"2025-04-16T04:50:43.454846Z","shell.execute_reply":"2025-04-16T04:50:44.445417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Biểu đồ theo dõi quá trình huấn luyện\n\nSau mỗi epoch, các chỉ số **Training Loss**, **Validation Accuracy**, và **Weighted AUC** được ghi lại và hiển thị bằng biểu đồ:\n\n1. **Loss per Epoch:**\n- Cho thấy mức giảm đều đặn của hàm mất mát qua từng vòng huấn luyện.\n- Điều này phản ánh rằng mô hình đang học hiệu quả từ dữ liệu.\n\n2. **Accuracy per Epoch:**\n- Mức độ chính xác của mô hình trên tập kiểm thử tăng dần theo thời gian.\n- Biểu đồ thể hiện rõ sự hội tụ sau một số epoch.\n\n3. **Weighted AUC per Epoch:**\n- Chỉ số AUC có trọng số (ưu tiên vùng TPR < 0.4) dùng để đánh giá mô hình tốt hơn trong việc phát hiện ảnh stego.\n- Sự tăng đều của đường này là dấu hiệu của mô hình đang cải thiện khả năng phân biệt ảnh cover và stego, đặc biệt ở vùng TPR thấp – quan trọng với nhiệm vụ steganalysis.\n\nBiểu đồ được lưu tại: `training_metrics.png`.","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-04-16T04:50:48.360420Z","iopub.execute_input":"2025-04-16T04:50:48.360997Z","iopub.status.idle":"2025-04-16T04:50:48.486180Z","shell.execute_reply.started":"2025-04-16T04:50:48.360976Z","shell.execute_reply":"2025-04-16T04:50:48.485181Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Chuẩn bị tập kiểm tra (Test set)\n\n- Tập test gồm các ảnh không nhãn từ thư mục `\"Test\"`, được gán nhãn tạm thời là `0` để phục vụ việc tạo `DataFrame`.\n- Ảnh test sau đó được đưa vào `SteganalysisDataset` và `DataLoader` với:\n  - **augment=False** vì không cần tăng cường dữ liệu khi kiểm thử.\n  - **shuffle=False** để giữ đúng thứ tự ảnh.\n  - **batch_size nhỏ** giúp tiết kiệm bộ nhớ.\n  - **num_workers=4** và **pin_memory=True** tăng hiệu năng tải dữ liệu.\n\n>chuẩn bị dữ liệu để mô hình có thể dự đoán nhãn ảnh stego/cover cho tập kiểm tra.","metadata":{}},{"cell_type":"code","source":"def predict(model, dataloader, device):\n    model.eval()\n    predictions = []\n    \n    with torch.no_grad():\n        for images, _ in tqdm(dataloader, desc=\"Predicting\"):\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-04-16T04:50:51.633038Z","iopub.execute_input":"2025-04-16T04:50:51.633566Z","iopub.status.idle":"2025-04-16T04:51:38.030000Z","shell.execute_reply.started":"2025-04-16T04:50:51.633544Z","shell.execute_reply":"2025-04-16T04:51:38.029287Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Dự đoán trên tập kiểm tra\n\n- Hàm `predict()` thực hiện suy luận trên mô hình đã huấn luyện:\n  - Chuyển ảnh sang `device` (CPU/GPU).\n  - Dùng `model` để tính xác suất mỗi ảnh là **ảnh stego** (`softmax[:, 1]`).\n- Kết quả là một danh sách các xác suất (float) cho mỗi ảnh.\n\n>tạo kết quả đầu ra (probability) để phân biệt giữa ảnh **che giấu (stego)** và **ảnh gốc (cover)**.","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-04-16T04:51:50.470693Z","iopub.execute_input":"2025-04-16T04:51:50.471023Z","iopub.status.idle":"2025-04-16T04:51:50.514667Z","shell.execute_reply.started":"2025-04-16T04:51:50.470997Z","shell.execute_reply":"2025-04-16T04:51:50.513927Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Tạo File Nộp Kết Quả (`submission.csv`)\n\n- **Lấy tên ảnh** từ đường dẫn: loại bỏ thư mục, chỉ giữ lại tên file `.jpg`.\n- **Chuyển đổi định dạng ID**:\n  - Loại bỏ đuôi `.jpg`, chuyển thành số nguyên để sắp xếp.\n  - Định dạng lại thành chuỗi 4 chữ số với đuôi `.jpg` (ví dụ: `0001.jpg`).\n- **Tạo DataFrame** gồm:\n  - `Id`: tên ảnh theo định dạng chuẩn.\n  - `Label`: xác suất ảnh là stego từ mô hình.\n- **Lưu file CSV**: sẵn sàng nộp cho hệ thống chấm điểm.","metadata":{}}]}