{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823},{"sourceType":"kernelVersion","sourceId":229463595},{"sourceType":"kernelVersion","sourceId":229467894},{"sourceType":"kernelVersion","sourceId":229469801},{"sourceType":"kernelVersion","sourceId":229583347},{"sourceType":"kernelVersion","sourceId":229589024},{"sourceType":"kernelVersion","sourceId":229590386},{"sourceType":"kernelVersion","sourceId":229670264},{"sourceType":"kernelVersion","sourceId":229670269}],"dockerImageVersionId":30918,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<a name='T'>\n\n<p style=\"padding: 20px;\n          background-color: black;\n          font-family: computermodern;\n          color: white;\n          font-size: 200%;\n          text-align: center;\n          border-radius: 40px 20px;\n          \">Generating the Ultra Augmented MNIST Dataset<br>\n          </p>\n<p style=\"font-family: computermodern;\n          color: #000000;\n          font-size: 175%;\n          text-align: center;\n          \">Created by Alexandre Le Mercier on the 24th of March 2025<br>\n             </p>\n\n\nThis is a simple notebook using my recently created **[Kaggle package](https://www.kaggle.com/code/alexandrelemercier/package-resources-for-image-dataset-augmentation?scriptVersionId=229590392)** for image data augmentation, applied to the famous [MNIST](https://www.kaggle.com/competitions/digit-recognizer/data) dataset.\n\n## 1. Loading and Understanding The Package","metadata":{}},{"cell_type":"code","source":"import os\nimport gc\nimport random\nimport shutil\nimport kagglehub\nfrom glob import glob\nimport matplotlib.pyplot as plt\nfrom keras.preprocessing.image import load_img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T22:51:14.661878Z","iopub.execute_input":"2025-03-25T22:51:14.662225Z","iopub.status.idle":"2025-03-25T22:51:31.603609Z","shell.execute_reply.started":"2025-03-25T22:51:14.662194Z","shell.execute_reply":"2025-03-25T22:51:31.602705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"package = kagglehub.package_import(f'alexandrelemercier/package-resources-for-image-dataset-augmentation')\n\nAugmenter = package.Augmenter\nload_images_from_csv = package.load_images_from_csv\nrandom_rotate = package.random_rotate\nrandom_shear = package.random_shear\nrandom_scale = package.random_scale\nben_graham_transform = package.ben_graham_transform\nrandom_gaussian_noise = package.random_gaussian_noise\nrandom_salt_pepper_noise = package.random_salt_pepper_noise\n\naugmenter = Augmenter(verbose=True)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-25T22:52:12.042769Z","iopub.execute_input":"2025-03-25T22:52:12.043173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dir(package)  # shows every class and function loaded with this package","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:01:41.806817Z","iopub.execute_input":"2025-03-25T12:01:41.807716Z","iopub.status.idle":"2025-03-25T12:01:41.815470Z","shell.execute_reply.started":"2025-03-25T12:01:41.807686Z","shell.execute_reply":"2025-03-25T12:01:41.814032Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"help(Augmenter) # You can now query a description of every feature mentionned above!","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:01:41.817076Z","iopub.execute_input":"2025-03-25T12:01:41.817487Z","iopub.status.idle":"2025-03-25T12:01:42.313492Z","shell.execute_reply.started":"2025-03-25T12:01:41.817444Z","shell.execute_reply":"2025-03-25T12:01:42.312329Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Extracting and Processing MNIST Images\n\nNow, let's start by extracting the JPG images from the MNIST CSV file:","metadata":{}},{"cell_type":"code","source":"load_images_from_csv(\"/kaggle/input/digit-recognizer/train.csv\", output_data_dir = \"MNIST\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:01:42.314399Z","iopub.execute_input":"2025-03-25T12:01:42.314685Z","iopub.status.idle":"2025-03-25T12:02:10.274163Z","shell.execute_reply.started":"2025-03-25T12:01:42.314663Z","shell.execute_reply":"2025-03-25T12:02:10.273084Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"To be further processed, each image from MNIST will be loaded in a `Collection` object from the [fast-skimage](https://pypi.org/project/fast-skimage/0.1.3/) library (for more information on how to use this package, you can refer to [this notebook](https://www.kaggle.com/code/alexandrelemercier/99-8-accuracy-on-brain-tumor-classification) which uses it to extract patterns from MRI images before applying a tabular multi-label classifier).","metadata":{}},{"cell_type":"code","source":"for n in range(0, 10):\n    augmenter.load_dataset_from_path(f\"/kaggle/working/MNIST/{n}\", f\"Label {n}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:02:10.275091Z","iopub.execute_input":"2025-03-25T12:02:10.275371Z","iopub.status.idle":"2025-03-25T12:04:02.239159Z","shell.execute_reply.started":"2025-03-25T12:02:10.275348Z","shell.execute_reply":"2025-03-25T12:04:02.237974Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now, we would like to save **10% of the dataset for validation**. We would like this validation set to only contain \"original\" MNIST images (not augmented ones) in order to estimate the **natural robustness** of models trained on augmented images in addition to traditionnal accuracy, precision and recall metrics, or even adversarial robustness (more details on the robustness types are given in the [package notebook](https://www.kaggle.com/code/alexandrelemercier/package-resources-for-image-dataset-augmentation?scriptVersionId=229590392)).","metadata":{}},{"cell_type":"code","source":"source_root = \"/kaggle/working/MNIST\"\nval_root = \"/kaggle/working/MNIST_validation\"\n\n# Ensure reproducibility (optional)\nrandom.seed(42)\n\n# Create the validation directory if it doesn't exist\nos.makedirs(val_root, exist_ok=True)\n\n# For each digit folder 0..9\nfor digit in range(10):\n    src_folder = os.path.join(source_root, str(digit))\n    dst_folder = os.path.join(val_root, str(digit))\n\n    # Ensure the validation subfolder exists\n    os.makedirs(dst_folder, exist_ok=True)\n\n    # Get all images in the source subfolder\n    images = [f for f in os.listdir(src_folder) if f.lower().endswith(('.jpg'))]\n\n    # Shuffle and choose ~10% to move\n    random.shuffle(images)\n    val_count = int(len(images) * 0.1)  # 10% of images\n    val_images = images[:val_count]\n\n    # Move those images to the validation folder\n    for img_name in val_images:\n        src_path = os.path.join(src_folder, img_name)\n        dst_path = os.path.join(dst_folder, img_name)\n        shutil.move(src_path, dst_path)\n\n    print(f\"Moved {val_count} images from '{src_folder}' to '{dst_folder}'.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:04:02.240054Z","iopub.execute_input":"2025-03-25T12:04:02.240310Z","iopub.status.idle":"2025-03-25T12:04:02.455262Z","shell.execute_reply.started":"2025-03-25T12:04:02.240289Z","shell.execute_reply":"2025-03-25T12:04:02.454275Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Crafting Augmented Images\n\nThe hardest part: **generating 10 augmented instances per original image**. The augmented images are created through random processes (e.g. random number of simultaneously applied augmentations per instance, and in what order). I selected 6 different augmentation types for this dataset (you can run `dir(package)` to see the full implemented augmentation functions list). Note that validation images won't be augmented in order to avoid bias when testing natural robustness.","metadata":{}},{"cell_type":"code","source":"augmentations = [\n    random_rotate,              \n    random_shear,                \n    random_scale,            \n    ben_graham_transform,       \n    random_gaussian_noise,\n    random_salt_pepper_noise\n]\n\n\nfor label in range(0, 10): # Takes ~2 minutes per label\n    augmenter.build_augmented_dataset_on_disk(\n        dataset_id=label,\n        output_dir=f\"Label {label}\",\n        scale_factor=10,  \n        augmentations=augmentations,\n        label=f\"Label {label}\",\n        to_df=False,\n        min_augmentations=1,\n        max_augmentations=6,\n        #csv_name=f\"{label}_augmented.csv\"\n    )\n    gc.collect() # Avoids some RAM problems","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:04:02.457875Z","iopub.execute_input":"2025-03-25T12:04:02.458165Z","iopub.status.idle":"2025-03-25T12:21:14.630703Z","shell.execute_reply.started":"2025-03-25T12:04:02.458132Z","shell.execute_reply":"2025-03-25T12:21:14.629281Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Final Modifications\n\nAt last, we move the original images into the augmented folders:","metadata":{}},{"cell_type":"code","source":"# For each label (0–9), copy original images into augmented folders\nfor label in range(10):\n    src_folder = f\"/kaggle/working/MNIST/{label}\"\n    dst_folder = f\"augmented_{label}\"\n    \n    # List original image files (assume jpg; change if different format)\n    original_images = glob(os.path.join(src_folder, \"*.jpg\"))\n    \n    # Copy each original image into the augmented folder\n    for img_path in original_images:\n        shutil.copy(img_path, dst_folder)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:21:14.632331Z","iopub.execute_input":"2025-03-25T12:21:14.632685Z","iopub.status.idle":"2025-03-25T12:21:19.909657Z","shell.execute_reply.started":"2025-03-25T12:21:14.632650Z","shell.execute_reply":"2025-03-25T12:21:19.908708Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We can now delete the old \"MNIST\" folder, which is no longer useful:","metadata":{}},{"cell_type":"code","source":"folder_to_delete = \"/kaggle/working/MNIST\"\n\nif os.path.exists(folder_to_delete):\n    shutil.rmtree(folder_to_delete)\n    print(f\"Deleted folder: {folder_to_delete}\")\nelse:\n    print(f\"Folder not found: {folder_to_delete}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:21:19.910756Z","iopub.execute_input":"2025-03-25T12:21:19.911199Z","iopub.status.idle":"2025-03-25T12:21:21.096428Z","shell.execute_reply.started":"2025-03-25T12:21:19.911159Z","shell.execute_reply":"2025-03-25T12:21:21.095362Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We can plot some of the images of our final dataset:","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(nrows=10, ncols=4, figsize=(8, 20))\n\nfor digit in range(10):\n    folder = f\"/kaggle/working/Label {digit}\"\n    if not os.path.isdir(folder):\n        print(f\"Folder not found: {folder}\")\n        continue\n\n    # Gather all JPG file names in this folder\n    images = [fn for fn in os.listdir(folder) if fn.lower().endswith('.jpg')]\n    # Sort or shuffle if desired, but we'll just take the first 4\n    images = images[:4]\n\n    for i, img_name in enumerate(images):\n        # Safely handle cases with fewer than 4 images\n        if i >= 4:\n            break\n        \n        img_path = os.path.join(folder, img_name)\n        # Load in grayscale\n        img = load_img(img_path, color_mode='grayscale')\n\n        # Display on the correct subplot\n        ax = axes[digit, i]\n        ax.imshow(img, cmap='gray')\n        ax.axis('off')\n        # Label each image with the digit\n        if i == 0:\n            ax.set_title(f\"Digit: {digit}\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-25T12:24:08.794272Z","iopub.execute_input":"2025-03-25T12:24:08.794829Z","iopub.status.idle":"2025-03-25T12:24:10.802388Z","shell.execute_reply.started":"2025-03-25T12:24:08.794792Z","shell.execute_reply":"2025-03-25T12:24:10.801282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<p style=\"padding: 20px;\n          background-color: green;\n          font-family: computermodern;\n          color: white;\n          font-size: 200%;\n          text-align: center;\n          border-radius: 40px 20px;\n          \">Thank you! If you found this useful please like 👍</p>","metadata":{}}]}